package fast.calc.rogue;

import fast.calc.AttackType;
import fast.calc.AttacksBlock;
import fast.calc.Buffs;
import fast.calc.Fast;
import fast.calc.Stats;
import fast.calc.WeaponStats;
import fast.data.BaseStats;
import fast.data.Spec;
import fast.data.Stat;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

public class FastRogue extends Fast {
	
	private HashMap<String,String> infoOutput = new HashMap<String,String>();

	private AttacksRogue attacks;
	private Map<String, Double> dps = new HashMap<String, Double>();
	private Map<String, Double> dpsOnlyCrit = new HashMap<String, Double>();
	
	// all specs
	private double sb_ori_cd = 180.;
	private double sb_actual_cd = sb_ori_cd;
	private static final double reaction_time = 0.5;
	private double mh_swings_per_sec;
	private double oh_swings_per_sec;
	private double total_hits;
	
	// assa
	private double vendetta_mult;
	private double shadow_blades_vendetta_mult;
	private double autoattack_vendetta_mult;
	private double finisher_with_4cp;
	private double finisher_with_5cp;
	private List<String> assa_finisher = Arrays.asList("envenom","rupture");
	
	// subtlety
	private double find_weakness_uptime;
	private double find_weakness_multiplier;
	private double find_weakness_damage_boost;
	private double mos_multiplier = 1.;
	private double ambushes_from_vanish;
	
	// combat
	private double ks_uptime;
	private double avg_bg_buff;
	private double avg_bg_buff_eviscerate;
	private double ks_ori_cd = 120.;
	private double ar_ori_cd = 180.;
	private double ks_actual_cd = ks_ori_cd;
	private double ar_actual_cd = ar_ori_cd;
	private List<String> combat_finisher = Arrays.asList("eviscerate","rupture");
	private List<String> combat_blade_flurry_attacks = 
			Arrays.asList(	"mh","oh","sb_mh","sb_oh","ks_mh","ks_oh",
							"eviscerate","sinister_strike","revealing_strike",
							"main_gauche");
	
	public FastRogue(BaseStats bs){
		stats = new Stats();
		stats.add(bs);
		stats.setRace(bs.getRace());
		stats.setBuffs(new Buffs()); // raid buffs
		stats.getBuffs().setBuffs(bs.getBuffs());
		stats.setSpec(bs.getSpec());
		
		stats.setMh(new WeaponStats(bs.getMH().dps, bs.getMH().speed, bs.getMH().type));
		if(bs.getOH() != null)
			stats.setOh(new WeaponStats(bs.getOH().dps, bs.getOH().speed, bs.getOH().type));
		
		stats.setGearBuffs(bs.getGearBuffs());
		stats.setGearBuffItemLevel(bs.getGearBuffItemLevel());
		if(!bs.getMH().getEnchant().equals(""))
			stats.getGearBuffs().getGearBuffs().add("mh_"+bs.getMH().getEnchant());
		if(!bs.getOH().getEnchant().equals(""))
			stats.getGearBuffs().getGearBuffs().add("oh_"+bs.getOH().getEnchant());
		
		stats.setSetItems(bs.getSetItems());
		stats.setTalents(bs.getTalents());
		stats.setGlyphs(bs.getGlyphs());
		
//		for(String gbs : stats.getGearBuffs().getGearBuffs()){
//			System.out.println("gb " + gbs);
//		}
		
//		for(String sbs : stats.getGearBuffs().getSetItems()){
//			System.out.println("setitems " + sbs);
//		}
//		for(String t : bs.getTalents()){
//			System.out.println("talents " + t);
//		}
//		for(String gl : bs.getGlyphs()){
//			System.out.println("||glyph " + gl);
//		}

		calc();
//		for(String tag : infoOutput.keySet()){
//			View.addLog(tag + ": " + infoOutput.get(tag));
//		}
		double ss_dmg = attacks.get_dmg("sinister_strike").getHitDamage();
		double rvs_dmg =  attacks.get_dmg("revealing_strike").getHitDamage();
		System.out.println("ss" + ss_dmg);
		System.out.println("rvs" + rvs_dmg);
		System.out.println("quotient" + ss_dmg/rvs_dmg);
	}
	
	public FastRogue(){
		stats = new Stats();
	}
	
	private void calc(){

		attacks = new AttacksRogue(this);
		dps1 = get_dps();
		dpsBreakdown = new HashMap<String,Double>(dps);
//		for(String attack : dps.keySet()){
//			System.out.println(attack + " " + dps.get(attack) + " (" + ((double)Math.round(dps.get(attack)/dps1*10000))/100 + "%)");
//		}
		System.out.println("DPS: " + dps1);
		// calc ep
		calc_ep();
	}
	
	protected double get_dps(){
		aps.clear();
		dps.clear();
		dpsOnlyCrit.clear();
		total_hits = 0.0;
		sb_actual_cd = sb_ori_cd;
		ar_actual_cd = ar_ori_cd;
		ks_actual_cd = ks_ori_cd;
		
		if(stats.getSpec() == Spec.Combat)
			calc_dps_combat4();
		else if(stats.getSpec() == Spec.Assassination){
			init_assassination();
			calc_dps_assassination3();
		}else if(stats.getSpec() == Spec.Subtlety)
			calc_dps_subtlety();
		
		for(String attack : aps.keySet()){
			if(	   (stats.getSpec() == Spec.Assassination && assa_finisher.contains(attack)) 
				|| (stats.getSpec() == Spec.Combat && combat_finisher.contains(attack))
			){
				AttacksBlock dmg = attacks.get_dmg(attack, 4);
				AttacksBlock dmg5 = attacks.get_dmg(attack, 5);
				//System.out.println("finisher with 4,5cp : " + finisher_with_4cp + " " + finisher_with_5cp);
				dmg.setHitDamage(dmg.getHitDamage()*finisher_with_4cp+dmg5.getHitDamage()*finisher_with_5cp);
				dmg.setCritDamage(dmg.getCritDamage()*finisher_with_4cp+dmg5.getCritDamage()*finisher_with_5cp);
				calc_dps_contribution(dmg,attacks.get_crit(attack));
			}else{
				calc_dps_contribution(attacks.get_dmg(attack),attacks.get_crit(attack));
			}
		}
		double dps1 = 0.0;
		for(String attack : dps.keySet()){
			dps1 += dps.get(attack);
		}
		return Math.max(dps1, 1.0);
	}
	
	@SuppressWarnings("unused")
	private void calc_dps_subtlety2() {
		double bs_ec = 35; // * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double hemo_ec = 30 * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double ambush_ec = 60 * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double ambush_sd_ec = 40 * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double evi_ec = 35; // * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double rup_ec = 25; // * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double snd_ec = 25;
		
		int finisher_cps = 5;
		// HAT
        double hat_cp_gen = 1 / 2.1;
		
		//while(true){
			double sb_duration = get_shadow_blades_duration();
			double sb_uptime = sb_duration / sb_actual_cd;
				        
	        hat_cp_gen += sb_uptime;

	        double hemo_interval = 24.;
			double energy_return_for_hemo = bs_ec - hemo_ec;
			double energy_regen = get_energy_regen() + energy_return_for_hemo / hemo_interval;
	        
	        //double cp_builder_interval = bs_ec / energy_regen;
			//double cp_per_cp_builder = 1 + cp_builder_interval * hat_cp_gen;
			
			// snd
			double snd_length = 6 + finisher_cps * 6; // with 5 cp 36sec
			double snd_net_energy_cost = snd_ec - 5 * finisher_cps;
			//double cp_builders_per_snd = finisher_cps / cp_per_cp_builder;
			//double total_snd_cost = snd_net_energy_cost + cp_builders_per_snd * bs_ec;
	        
			// rupture
			double rupture_length = 4 + finisher_cps * 4;
	        double rupture_net_energy_cost = rup_ec - 5 * finisher_cps;
	        //double cp_builders_per_rupture = finisher_cps / cp_per_cp_builder;
	        //double total_rupture_cost = rupture_net_energy_cost + cp_builders_per_rupture * bs_ec;
			double rupture_per_snd = snd_length / rupture_length;
			
			double total_cycle_regen = snd_length * energy_regen;
			
	        // vanish ambushes
	        double vanish_cooldown = 180.;
	        double prep = 0.;
	        if(stats.hasTalent("preparation"))
	        	prep = 1.;
	        ambushes_from_vanish = 1. / (vanish_cooldown + reaction_time) + prep / (FIGHT_LENGTH + reaction_time);
	        find_weakness_uptime = 10 * ambushes_from_vanish;
	        
	        double cp_per_ambush = 2;
	        // ambush cost no energy due to talent
	        double extra_cp_per_snd = (hat_cp_gen + ambushes_from_vanish * (cp_per_ambush + 2)) * snd_length;
	        double cp_used_on_buffs = 5. + rupture_per_snd * 5.;
	        double bonus_eviscerates_per_snd = (extra_cp_per_snd - cp_used_on_buffs) / finisher_cps;
	        double energy_spent_on_bonus_finishers =  bonus_eviscerates_per_snd * (evi_ec - 5 * finisher_cps);
	        double energy_for_evis_spam = total_cycle_regen - rupture_net_energy_cost * rupture_per_snd - snd_net_energy_cost - energy_spent_on_bonus_finishers;
	        double total_cost_of_extra_eviscerate = finisher_cps * bs_ec + evi_ec - 5 * finisher_cps;
	        double extra_eviscerates_per_snd = energy_for_evis_spam / total_cost_of_extra_eviscerate;
//	        System.out.println("ambushes_from_vanish " + ambushes_from_vanish);
//	        System.out.println("hat_cp_gen " + hat_cp_gen);
//	        System.out.println("extra_cp_per_snd " + extra_cp_per_snd);
//	        System.out.println("cp_used_on_buffs " + cp_used_on_buffs);
//	        System.out.println("bonus_eviscerates_per_snd " + bonus_eviscerates_per_snd);
//	        System.out.println("energy_spent_on_bonus_finishers " + energy_spent_on_bonus_finishers);
//	        System.out.println("energy_for_evis_spam " + energy_for_evis_spam);
//	        System.out.println("extra_eviscerates_per_snd " + extra_eviscerates_per_snd);
	        
	        
//	        double energy_spent = 5. * extra_eviscerates_per_snd / snd_length * bs_ec;
//	        energy_spent += (bonus_eviscerates_per_snd + extra_eviscerates_per_snd) / snd_length * (evi_ec - 5 * finisher_cps);
//	        System.out.println("energy_spent " + energy_spent);
//	        System.out.println("energy_spent_per_snd " + energy_spent*snd_length);
//	        System.out.println("total_cycle_regen " + total_cycle_regen);
//	        System.out.println("energy_regen " + energy_regen);
	        
	        aps.put("backstab", 5 * extra_eviscerates_per_snd / snd_length);
	        aps.put("eviscerate", (bonus_eviscerates_per_snd + extra_eviscerates_per_snd) / snd_length );
	        aps.put("ambush", ambushes_from_vanish);
	        
	        // to this point its correct
	        double shadow_dance_duration = 8.;
	        double shadow_dance_frequency = 1. / (60 + reaction_time);
	
	        double shadow_dance_bonus_cp_regen = shadow_dance_duration * hat_cp_gen + 2; // 2 = premed
	        double shadow_dance_bonus_eviscerates = shadow_dance_bonus_cp_regen / finisher_cps;
	        double shadow_dance_bonus_eviscerate_cost = shadow_dance_bonus_eviscerates * (evi_ec - 5 * finisher_cps);
	        double shadow_dance_available_energy = shadow_dance_duration * energy_regen - shadow_dance_bonus_eviscerate_cost;

	        double shadow_dance_eviscerate_cost = 5 / cp_per_ambush * ambush_sd_ec + (evi_ec - 5 * finisher_cps);
	        double shadow_dance_eviscerates_for_period = shadow_dance_available_energy / shadow_dance_eviscerate_cost;

	        double base_bonus_cp_regen = shadow_dance_duration * hat_cp_gen;
	        double base_bonus_eviscerates = base_bonus_cp_regen / 5;
	        double base_bonus_eviscerate_cost = base_bonus_eviscerates * (evi_ec - 5 * finisher_cps);
	        double base_available_energy = shadow_dance_duration * energy_regen - base_bonus_eviscerate_cost;

	        double base_eviscerates_for_period = base_available_energy / total_cost_of_extra_eviscerate;

	        double shadow_dance_extra_eviscerates = shadow_dance_eviscerates_for_period + shadow_dance_bonus_eviscerates - base_eviscerates_for_period - base_bonus_eviscerates;
	        double shadow_dance_extra_ambushes = 5 / cp_per_ambush * shadow_dance_eviscerates_for_period;
	        double shadow_dance_replaced_cp_builders = 5 * base_eviscerates_for_period;
	        
	        aps.put("backstab", aps.get("backstab") - shadow_dance_replaced_cp_builders * shadow_dance_frequency);
	        aps.put("ambush", aps.get("ambush") + shadow_dance_extra_ambushes * shadow_dance_frequency);
	        aps.put("eviscerate", aps.get("eviscerate") + shadow_dance_extra_eviscerates * shadow_dance_frequency);
	
	        find_weakness_uptime += (10 + shadow_dance_duration - reaction_time) * shadow_dance_frequency;
	        
	        aps.put("rupture", 0.5);
	        aps.put("hemorrhage", 1. / hemo_interval);
	        double ticks_per_second = Math.min(1. / 3, 8 / hemo_interval);
	        aps.put("hemorrhage_dot", ticks_per_second);
			aps.put("backstab", aps.get("backstab") - aps.get("hemorrhage"));
	        //
			
			if(stats.hasTalent("marked_for_death")){
				double extra_evis_per_sec = 1. / 60.;
				aps.put("eviscerate", aps.get("eviscerate") + extra_evis_per_sec);
			}
				
			this.white_attacks(sb_uptime);
			
			double total_mh_hits = aps.get("mh") + aps.get("eviscerate") + aps.get("sb_mh");
			total_mh_hits += aps.get("hemorrhage") + aps.get("backstab") + aps.get("ambush");
			double total_oh_hits = aps.get("oh") + aps.get("sb_oh");

			//if(Math.abs(total_hits - (total_mh_hits + total_oh_hits)) < PRECISION)
			//	break;

			aps.put("deadly_poison", 1./3);
			total_hits = aps.get("mh") + aps.get("oh") + aps.get("eviscerate");
			total_hits += aps.get("hemorrhage") + aps.get("backstab") + aps.get("ambush");
			total_hits +=  aps.get("sb_mh") + aps.get("sb_oh");
			
			double ip_per_sec = total_mh_hits * 0.3 * stats.getMhPoisonHitChance();
			ip_per_sec += total_oh_hits * 0.3 * stats.getOhPoisonHitChance();
			
			aps.put("instant_poison", ip_per_sec);
			
			this.procs(total_mh_hits,total_oh_hits, ip_per_sec);
		//}
		
		// find weakness
		find_weakness_damage_boost = 0.;
        find_weakness_multiplier = 1.;
        double armor_value = Stats.BOSS_ARMOR;
        double armor_reduction = (1 - .7);
        find_weakness_damage_boost = this.get_eff_dmg(armor_reduction * armor_value) / this.get_eff_dmg();
        find_weakness_multiplier = 1 + (find_weakness_damage_boost - 1) * find_weakness_uptime;
        
        double mos_value = .1;
        double mos_triggers = 1 / (vanish_cooldown + reaction_time);
        mos_triggers += prep / (360. + reaction_time * 3);
        double subterfuge = 0;
        mos_multiplier = 1. + mos_value * (6 + 3 * subterfuge) * mos_triggers;
        //System.out.println("mos_multiplier" + mos_multiplier);
	}
	
	private HashMap<String, Double> subtlety_rota(double rota_duration, boolean shadow_blades, double energy_regen, double flat_extra_energy){
		HashMap<String,Double> props = new HashMap<String,Double>();
		
		HashMap<String,Double> white_attacks = this.white_attacks(shadow_blades);
		
		double mh_hits = 0.0;
		double oh_hits = 0.0;
		if(shadow_blades){
			mh_hits = white_attacks.get("sb_mh");
			oh_hits = white_attacks.get("sb_oh");
		}else{
			mh_hits = white_attacks.get("mh");
			oh_hits = white_attacks.get("oh");
		}
		
		double energy_available = energy_regen * rota_duration + flat_extra_energy;
		double extra_cps = 1 / 2.1 * rota_duration;
		
		double avg_cps_per_finisher = 0.0;
		double bs_needed = 0.0;
		double finisher_with_4cp = 0.0;
		double finisher_with_5cp = 0.0;
		double active_t15_4pc = 0.;
		if(stats.hasTalent("anticipation")){
			avg_cps_per_finisher = 5.0;
			if(shadow_blades){
				active_t15_4pc = 1.;
				
				double cp_per_bs = 1. + 1.;
				bs_needed = avg_cps_per_finisher / cp_per_bs;
			} else {
				double cp_per_bs = 1.;
				bs_needed = avg_cps_per_finisher / cp_per_bs;
			}
			finisher_with_4cp = 0.0;
			finisher_with_5cp = 1.0;
		}
		else {
			if(shadow_blades){
				active_t15_4pc = 1.;
				
				double sb_bs0_2_4 = 1;
				
				bs_needed = 2 * sb_bs0_2_4;
				
				finisher_with_4cp = sb_bs0_2_4;
				finisher_with_5cp = 1;
				avg_cps_per_finisher = 5;
			} else {
				double ss0_1_2_3_4_5 = 1; // 5
				
				bs_needed  = 5 * ss0_1_2_3_4_5;
				
				// how many finisher ends with 4 and 5 cps
				finisher_with_4cp = 0;
				finisher_with_5cp = 1 - finisher_with_4cp;
				avg_cps_per_finisher = 4 + finisher_with_5cp;
			}
		}
		double bs_ec = (35 - active_t15_4pc * get_t15_4pc(35)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double evi_ec = (35 - active_t15_4pc * get_t15_4pc(35)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double rup_ec = (25 - active_t15_4pc * get_t15_4pc(25)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double snd_ec = 25 - active_t15_4pc * get_t15_4pc(25);
		
		double avg_rupture_length = get_rupture_duration(avg_cps_per_finisher);
		double avg_snd_length = get_snd_duration(avg_cps_per_finisher);
		double avg_relentless_strikes = 5 * avg_cps_per_finisher;

		double energy_cost = bs_needed * bs_ec;
		double energy_cost_rup = energy_cost + rup_ec - avg_relentless_strikes;
		double energy_cost_evi = energy_cost + evi_ec - avg_relentless_strikes;
		double energy_cost_snd = energy_cost + snd_ec - avg_relentless_strikes;
		double ruptures = rota_duration / avg_rupture_length;
		double snds = rota_duration / avg_snd_length;
		double energy_left_for_evi = energy_available - ruptures * energy_cost_rup;
		energy_left_for_evi -= snds * energy_cost_snd;
		
		System.out.println("energy_left_for_evi " + energy_left_for_evi);
		System.out.println("energy_cost_evi " + energy_cost_evi);
		
		System.out.println("extra_cps " + extra_cps);
		if(!stats.hasTalent("anticipation") && shadow_blades){
			
		}
		
		double attacks_so_far = (ruptures + snds) * (1 + bs_needed);
		double gcd = 1.0;
		double time_left = rota_duration - attacks_so_far * gcd;
		double sum_attacks_per_evi = bs_needed + 1;
		double evis_time_limited = time_left / (sum_attacks_per_evi * gcd);
		double evis_energy_limited = energy_left_for_evi / energy_cost_evi;
		
		double evis = evis_energy_limited;
		if(evis_time_limited < evis_energy_limited){
			evis = evis_time_limited;
			double energy_lost = energy_left_for_evi - evis * energy_cost_evi;
			//View.addConsole("EP "+ stats.calcEP + " - energy lost due to capping: " + energy_lost);
			String msg = "["+stats.calcEP + "] energy lost ";
			if(shadow_blades)
				msg += "during SB";
			else
				msg += "during normal rota";
			infoOutput.put(msg, energy_lost+"");
		}
		
		double ss_per_cycle = bs_needed * (snds + ruptures + evis);
		
		props.put("ss_per_cycle", ss_per_cycle);
		props.put("evi_per_cycle", evis);
		props.put("rup_per_cycle", ruptures);
		props.put("energy_cost_evi", energy_cost_evi);
		props.put("avg_cps_per_finisher", avg_cps_per_finisher);
		props.put("ss_per_finisher", bs_needed);
		props.put("finisher_with_4cp", finisher_with_4cp);
		props.put("finisher_with_5cp", finisher_with_5cp);
		props = combineProps(props,white_attacks);
		return props;
	}
	
	private void calc_dps_subtlety_new(){
		while(true) {
			double sb_duration = get_shadow_blades_duration();
			double energy_regen = get_energy_regen();
			
			// hemo will never be used under shadowblades
			double hemo_duration = 24.;
			double hemo_ec = 30 * (0.8 + 0.2 / this.stats.getMhStyleHitChance());
			double hemo_interval = 1. / hemo_duration;
			energy_regen -= hemo_ec / hemo_duration;
			
			double hemo_per_sec = hemo_interval;
			double extra_evis_per_sec = hemo_per_sec * 1 / 5;
			
			// marked for death will never be used during shadowblads
			if(stats.hasTalent("marked_for_death")){
				extra_evis_per_sec += 1. / 60.;
			}
			double evi_ec = 35 * (0.8 + 0.2/this.stats.getMhStyleHitChance());
			energy_regen -= extra_evis_per_sec * (evi_ec - 5 * 5); // relentless strike
			
			double pooling_time = 0;
			if(stats.getGearBuffs().has("subtlety_pool_before_dance"))
				pooling_time = pooling_time(energy_regen, 100.);

			// AR
			

			HashMap<String,Double> subtlety_sb;
			HashMap<String,Double> subtlety_normal;
			HashMap<String,Double> subtlety;
			
			// for shadow_blades up
			subtlety_sb = subtlety_rota(sb_duration, true, energy_regen, pooling_time * energy_regen);
			// for shadow_blades down
			subtlety_normal = subtlety_rota(sb_actual_cd-sb_duration, false, energy_regen, pooling_time * -energy_regen);
			//combine
			subtlety = combineProps(subtlety_normal,subtlety_sb);
			
			double ss_per_sec = subtlety.get("ss_per_cycle") / sb_actual_cd;
			double evis_per_sec = subtlety.get("evi_per_cycle") / sb_actual_cd;
			double rup_per_sec = subtlety.get("rup_per_cycle") / sb_actual_cd;
			if(stats.getGearBuffs().has("combat_use_rupture")){
				double rup_ticks_every = Math.min(0.5, rup_per_sec*12.);
				aps.put("rupture", rup_ticks_every);
			}
			
			finisher_with_4cp = sb_duration * subtlety_sb.get("finisher_with_4cp") + (sb_actual_cd-sb_duration) * subtlety_normal.get("finisher_with_4cp");
			finisher_with_4cp /= sb_actual_cd;
			finisher_with_5cp = sb_duration * subtlety_sb.get("finisher_with_5cp") + (sb_actual_cd-sb_duration) * subtlety_normal.get("finisher_with_5cp");
			finisher_with_5cp /= sb_actual_cd;
			double avg_finisher_cps = 4 + finisher_with_5cp;
			
			aps.put("sinister_strike", ss_per_sec);
			aps.put("eviscerate", evis_per_sec + extra_evis_per_sec);
			aps.put("hemorrhage", hemo_per_sec);
			
			aps.put("mh", (sb_actual_cd-sb_duration) * subtlety.get("mh") / sb_actual_cd);
			aps.put("oh", (sb_actual_cd-sb_duration) * subtlety.get("oh") / sb_actual_cd);
			aps.put("sb_mh", sb_duration * subtlety.get("sb_mh") / sb_actual_cd);
			aps.put("sb_oh", sb_duration * subtlety.get("sb_oh") / sb_actual_cd);
			
			//double mh_white_hits = aps.get("mh") + aps.get("sb_mh");
			double total_mh_hits = aps.get("mh") + aps.get("eviscerate") + aps.get("sb_mh");
			total_mh_hits += aps.get("sinister_strike") + aps.get("hemorrhage");
			double total_oh_hits = aps.get("oh") + aps.get("sb_oh");
			
			if(Math.abs(total_hits - (total_mh_hits + total_oh_hits)) < PRECISION)
				break;

			total_hits = aps.get("mh") + aps.get("oh") + aps.get("eviscerate");
			total_hits += aps.get("sinister_strike") + aps.get("hemorrhage");
			total_hits += aps.get("sb_mh") +  aps.get("sb_oh");
			
			double ip_per_sec = total_mh_hits * 0.3 * stats.getMhPoisonHitChance();
			ip_per_sec += total_oh_hits * 0.3 * stats.getOhPoisonHitChance();
			
			aps.put("instant_poison", ip_per_sec);
			aps.put("deadly_poison", 1./3);
			
			
			this.procs(total_mh_hits,total_oh_hits, ip_per_sec);
		}	
	}
		
	private void calc_dps_subtlety() {
		double sb_duration = get_shadow_blades_duration();
		double sb_uptime = sb_duration / sb_actual_cd;
		
		double bs_ec = (35 - sb_uptime * get_t15_4pc(35)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double hemo_ec = (30 - sb_uptime * get_t15_4pc(30)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double ambush_ec = (60 - sb_uptime * get_t15_4pc(60)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		if(stats.hasTalent("shadow_focus"))
			ambush_ec *= 0.25;
		double ambush_sd_ec = (40 - sb_uptime * get_t15_4pc(40)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double evi_ec = (35 - sb_uptime * get_t15_4pc(35)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double rup_ec = (25 - sb_uptime * get_t15_4pc(25)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double snd_ec = (25 - sb_uptime * get_t15_4pc(25));
				
		int finisher_cps = 5;
		
		while(true){
			
			// HAT
	        double hat_cp_gen = 1 / 2.1;
	        
	        hat_cp_gen += sb_uptime;

	        double hemo_interval = 24.;
			double energy_return_for_hemo = bs_ec - hemo_ec;
			double energy_regen = get_energy_regen() + energy_return_for_hemo / hemo_interval;
	        
	        double cp_builder_interval = bs_ec / energy_regen;
			double cp_per_cp_builder = 1 + cp_builder_interval * hat_cp_gen;
			
			double eviscerate_net_energy_cost = evi_ec - 5 * finisher_cps;
			double eviscerate_net_cp_cost = finisher_cps - eviscerate_net_energy_cost * hat_cp_gen / energy_regen;
			double cp_builders_per_eviscerate = eviscerate_net_cp_cost / cp_per_cp_builder;
			double total_eviscerate_cost = eviscerate_net_energy_cost + cp_builders_per_eviscerate * bs_ec;
			double total_eviscerate_duration = total_eviscerate_cost / energy_regen;
			
			// rupture
			double rupture_length = 4 + finisher_cps * 4;
			if(stats.getGearBuffs().hasSet("t15_2pc"))
				rupture_length += 4.;
			
	        double rupture_net_energy_cost = rup_ec - 5 * finisher_cps;
	        double rupture_net_cp_cost = 5; //- rupture_net_energy_cost * hat_cp_gen / get_energy_regen();
	        double cp_builders_under_previous_rupture = .5 * total_eviscerate_duration / bs_ec;
	        double cp_gained_under_previous_rupture = cp_builders_under_previous_rupture * cp_per_cp_builder;
	        double cp_needed_outside_rupture = rupture_net_cp_cost - cp_gained_under_previous_rupture;
	        double cp_builders_after_rupture = cp_needed_outside_rupture / cp_per_cp_builder;
	        double energy_spent_after_rupture = cp_builders_after_rupture * bs_ec + rupture_net_energy_cost;
	
	        double cycle_length = rup_ec + energy_spent_after_rupture / energy_regen;
	        double total_cycle_regen = rupture_length * energy_regen + energy_spent_after_rupture;
			
			double snd_length = 6 + finisher_cps * 6; // with 5 cp 36sec
			if(stats.getGearBuffs().hasSet("t15_2pc"))
				snd_length += 6.;
			
			// snd
			double snd_net_energy_cost = snd_ec - 5 * finisher_cps;
			double cp_builders_per_snd = 5 / cp_per_cp_builder;
			double total_snd_cost = snd_net_energy_cost + cp_builders_per_snd * bs_ec;
	        double snd_build_time =  total_snd_cost / energy_regen;
	        double hat_cp_per_snd = snd_build_time * hat_cp_gen;
	        
	        double snd_size = hat_cp_per_snd + cp_builders_per_snd;
	        double snd_duration = snd_length;
	  
	        double snd_per_cycle = cycle_length / snd_duration;
	        
	        //
	        
	        double vanish_cooldown = 180.;
	        double prep = 0.;
	        if(stats.hasTalent("preparation"))
	        	prep = 1.;
	        ambushes_from_vanish = 1. / (vanish_cooldown + reaction_time) + prep / (FIGHT_LENGTH + reaction_time);
	        find_weakness_uptime = 10 * ambushes_from_vanish;
	        
	        double cp_per_ambush = 2;
	        
	        double bonus_cp_per_cycle = (hat_cp_gen + ambushes_from_vanish * (cp_per_ambush + 2)) * cycle_length;
	        double cp_used_on_buffs = finisher_cps + snd_size * snd_per_cycle;
	        double bonus_eviscerates = (bonus_cp_per_cycle - cp_used_on_buffs) / finisher_cps;
	        double energy_spent_on_bonus_finishers = rup_ec + snd_ec * snd_per_cycle + evi_ec * bonus_eviscerates - (5 + snd_size * snd_per_cycle + 5 * bonus_eviscerates) * 5 + cycle_length * ambushes_from_vanish * ambush_ec;
	        double energy_for_evis_spam = total_cycle_regen - energy_spent_on_bonus_finishers;
	        double total_cost_of_extra_eviscerate = finisher_cps * bs_ec + evi_ec - 5 * finisher_cps;
	        double extra_eviscerates_per_cycle = energy_for_evis_spam / total_cost_of_extra_eviscerate;
	        			
	        aps.put("backstab", 5 * extra_eviscerates_per_cycle / cycle_length);
	        aps.put("eviscerate", (bonus_eviscerates + extra_eviscerates_per_cycle) / cycle_length );
	        aps.put("ambush", ambushes_from_vanish);
	        
	        double shadow_dance_duration = 8.;
	        double shadow_dance_frequency = 1. / (60 + reaction_time);
	
	        double shadow_dance_bonus_cp_regen = shadow_dance_duration * hat_cp_gen + 2; // 2 = premed
	        double shadow_dance_bonus_eviscerates = shadow_dance_bonus_cp_regen / finisher_cps;
	        double shadow_dance_bonus_eviscerate_cost = shadow_dance_bonus_eviscerates * (evi_ec - 5 * finisher_cps);
	        double shadow_dance_available_energy = shadow_dance_duration * energy_regen - shadow_dance_bonus_eviscerate_cost;

	        double shadow_dance_eviscerate_cost = 5 / cp_per_ambush * ambush_sd_ec + (evi_ec - 5 * finisher_cps);
	        double shadow_dance_eviscerates_for_period = shadow_dance_available_energy / shadow_dance_eviscerate_cost;
	          
	        // calculate how much energy i would have when shadow dance not active
	        double base_bonus_cp_regen = shadow_dance_duration * hat_cp_gen;
	        double base_bonus_eviscerates = base_bonus_cp_regen / 5;
	        double base_bonus_eviscerate_cost = base_bonus_eviscerates * (evi_ec - 5 * finisher_cps);
	        double base_available_energy = shadow_dance_duration * energy_regen - base_bonus_eviscerate_cost;
	        double base_eviscerates_for_period = base_available_energy / total_cost_of_extra_eviscerate;

	        double shadow_dance_extra_eviscerates = shadow_dance_eviscerates_for_period + shadow_dance_bonus_eviscerates - base_eviscerates_for_period - base_bonus_eviscerates;
	        double shadow_dance_extra_ambushes = 5 / cp_per_ambush * shadow_dance_eviscerates_for_period;
	        //System.out.println(shadow_dance_extra_ambushes);
	        double shadow_dance_replaced_cp_builders = 5 * base_eviscerates_for_period;
	        
	        aps.put("backstab", aps.get("backstab") - shadow_dance_replaced_cp_builders * shadow_dance_frequency);
	        aps.put("ambush", aps.get("ambush") + shadow_dance_extra_ambushes * shadow_dance_frequency);
	        aps.put("eviscerate", aps.get("eviscerate") + shadow_dance_extra_eviscerates * shadow_dance_frequency);
	
	        find_weakness_uptime += (10 + shadow_dance_duration - reaction_time) * shadow_dance_frequency;
	        
	        aps.put("rupture", 0.5);
	        aps.put("hemorrhage", 1. / hemo_interval);
	        double ticks_per_second = Math.min(1. / 3, 8 / hemo_interval);
	        aps.put("hemorrhage_dot", ticks_per_second);
			aps.put("backstab", aps.get("backstab") - aps.get("hemorrhage"));
			
			if(stats.hasTalent("marked_for_death")){
				double extra_evis_per_sec = 1. / 60.;
				aps.put("eviscerate", aps.get("eviscerate") + extra_evis_per_sec);
			}
				
			this.white_attacks(sb_uptime);
			
			double total_mh_hits = aps.get("mh") + aps.get("eviscerate") + aps.get("sb_mh");
			total_mh_hits += aps.get("hemorrhage") + aps.get("backstab") + aps.get("ambush");
			double total_oh_hits = aps.get("oh") + aps.get("sb_oh");

			if(Math.abs(total_hits - (total_mh_hits + total_oh_hits)) < PRECISION)
				break;

			total_hits = aps.get("mh") + aps.get("oh") + aps.get("eviscerate");
			total_hits += aps.get("hemorrhage") + aps.get("backstab") + aps.get("ambush");
			total_hits +=  aps.get("sb_mh") + aps.get("sb_oh");
			
			double ip_per_sec = total_mh_hits * 0.3 * stats.getMhPoisonHitChance();
			ip_per_sec += total_oh_hits * 0.3 * stats.getOhPoisonHitChance();
			
			aps.put("instant_poison", ip_per_sec);
			aps.put("deadly_poison", 1./3);
			
			this.procs(total_mh_hits,total_oh_hits, ip_per_sec);
		}
		
		// find weakness
		find_weakness_damage_boost = 0.;
        find_weakness_multiplier = 1.;
        double armor_value = Stats.BOSS_ARMOR;
        double find_weakness_reduction = 1.;
        double armor_reduction = (1 - find_weakness_reduction);
        find_weakness_damage_boost = this.get_eff_dmg(armor_reduction * armor_value) / this.get_eff_dmg();
        find_weakness_multiplier = 1 + (find_weakness_damage_boost - 1) * find_weakness_uptime;
        
        double mos_value = .1;
        double mos_triggers = 1 / (180. + reaction_time);
        double prep = 0.;
        if(stats.hasTalent("preparation"))
        	prep = 1.;
        mos_triggers += prep / (360. + reaction_time * 3);
        double subterfuge = 0;
        if(stats.hasTalent("subterfuge"))
        	subterfuge = 1.;
        mos_multiplier = 1. + mos_value * (6 + 3 * subterfuge) * mos_triggers;
	}
	
	private HashMap<String, Double> assa_rotation(double duration, boolean shadow_blades,double energy_regen, double extra_energy_from_pooling){
		HashMap<String,Double> props = new HashMap<String,Double>();
		
		double rota_duration = duration;
		
		double energy_available = energy_regen * rota_duration + extra_energy_from_pooling;
		
		// seal fate
		double crit_chance_mh = attacks.get_crit_mutilate_mh();
		double crit_chance_oh = attacks.get_crit_mutilate_oh();
		double mut_crit = 1 - ( 1 - crit_chance_mh) * ( 1 - crit_chance_oh);
		double dp_crit = attacks.get_crit_dispatch();
		
		double avg_cps_per_finisher = 0.0;
		double muti_needed = 0.0;
		double bs_needed = 0.0;
		double finisher_with_4cp = 0.0;
		double finisher_with_5cp = 0.0;
		double active_t15_4pc = 0.;
		if(shadow_blades){
			active_t15_4pc = 1.;
			double sb_mut0_3_5 = 0.7 * (1-mut_crit)*0.7*1;
			double sb_mut0_3_5dp = 0.7 *(1-mut_crit)*0.3*1;
			double sb_mut0_4 = 0.7 *mut_crit*0.7;
			double sb_mut0_4_5dp = 0.7*mut_crit*0.3*1;
			// is it better to finish with 4 and use the free dp in next rota???
			
			double sb_mut0_3_5dp2 = 0.3*dp_crit*1;
			double sb_mut0_2_5 = 0.3*(1-dp_crit)*1;
			
			muti_needed = 2 * sb_mut0_3_5 + 1 * sb_mut0_3_5dp + 1 * sb_mut0_4;
			muti_needed += 1 * sb_mut0_4_5dp + 1 * sb_mut0_3_5dp2 + 1 * sb_mut0_2_5;
			
			bs_needed = 1 * sb_mut0_3_5dp + 1 * sb_mut0_4_5dp + 1 * sb_mut0_3_5dp2 + 1 * sb_mut0_2_5;
			
			finisher_with_4cp = sb_mut0_4;
			finisher_with_5cp = 1 - finisher_with_4cp;
			avg_cps_per_finisher = 4 + finisher_with_5cp;
		} else {
			double mut0_2_4 = 0.7*(1-mut_crit)*0.7*(1-mut_crit)*0.7;//ok
			double mut0_2_4dp = 0.7*(1-mut_crit)*0.3*dp_crit;//ok
			double mut0_2_3_5 = 0.7*(1-mut_crit)*0.3*(1-dp_crit)*1;//ok
			double mut0_2_4_5 = 0.7*(1-mut_crit)*0.7*(1-mut_crit) * 0.3 * 1;//ok
			double mut0_2_5 = 0.7*(1-mut_crit)*0.7*mut_crit;//ok
			
			double mut0_3_5 = 0.7 * mut_crit * 0.7 * 1;//ok
			double mut0_3_5dp = 0.7 * mut_crit * 0.3 * dp_crit;//ok
			double mut0_3_4 = 0.7 * mut_crit*0.3*(1-mut_crit);//ok
	
			double mut0_1_3_5 = 0.3 * (1-dp_crit) * (1-mut_crit)*0.7*1;//ok
			double mut0_1_3_4 = 0.3 * (1-dp_crit) * (1-mut_crit)*0.3 * (1-dp_crit);
			double mut0_1_3_5dp = 0.3 * (1-dp_crit) * (1-mut_crit)*0.3 * dp_crit;
			double mut0_1_4 = 0.3 * (1-dp_crit) * mut_crit*0.7;
			double mut0_1_4_5 = 0.3 * (1-dp_crit) * mut_crit * 0.3;
			double mut0_2_4dp2 = 0.3 * dp_crit * (1-mut_crit) * 0.7;
			double mut0_2_4_5dp2 = 0.3 * dp_crit * (1-mut_crit) * 0.3 * 1;
			double mut0_2_5dp2 = 0.3 * dp_crit * mut_crit;
			
			muti_needed = 2 * mut0_2_4 + 2 * mut0_2_5 + 2 * mut0_3_5;
			muti_needed += 2 * mut0_1_3_5 + 1 * mut0_1_3_4 + 1 * mut0_1_3_4;
			muti_needed += 1 * mut0_1_4 + 1 * mut0_1_4_5 + 2 * mut0_2_3_5;
			muti_needed += 2 * mut0_2_4_5 + 1 * mut0_3_4 + 1 * mut0_2_4dp;
			muti_needed += 1 * mut0_3_5dp + 1 * mut0_1_3_5dp + 1 * mut0_2_4dp2;
			muti_needed += 1 * mut0_2_4_5dp2 + 1 * mut0_2_5dp2;
			
			bs_needed = 1 * mut0_1_3_5 + 2 * mut0_1_3_4 + 1 * mut0_1_4;
			bs_needed += 2 * mut0_1_4_5 + 1 * mut0_2_3_5 + 1 * mut0_2_4_5;
			bs_needed += 1 * mut0_3_4 + 1 * mut0_2_4dp + 1 * mut0_3_5dp;
			bs_needed += 2 * mut0_1_3_5dp + 1 * mut0_2_4dp2 + 2 * mut0_2_4_5dp2;
			bs_needed += 1 * mut0_2_5dp2;
				
			// how many finisher ends with 4 and 5 cps
			finisher_with_4cp = mut0_2_4 + mut0_2_4dp + mut0_1_3_4 + mut0_1_4 + mut0_2_4dp2 + mut0_3_4;
			finisher_with_5cp = 1 - finisher_with_4cp;
			avg_cps_per_finisher = 4 + finisher_with_5cp;
		}
		double muti_ec = (55 - active_t15_4pc * get_t15_4pc(55)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double env_ec = (35 - active_t15_4pc * get_t15_4pc(35)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double rup_ec = (25 - active_t15_4pc * get_t15_4pc(25)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		
		double avg_rupture_length = 4 + 4 * avg_cps_per_finisher;
		if(stats.getGearBuffs().hasSet("t15_2pc"))
			avg_rupture_length += 4.;
		double avg_relentless_strikes = 5 * avg_cps_per_finisher;

		double energy_cost = muti_needed * muti_ec;
		double energy_cost_rup = energy_cost + rup_ec - avg_relentless_strikes;
		double energy_cost_env = energy_cost + env_ec - avg_relentless_strikes;
		
		double ruptures = rota_duration / avg_rupture_length;
		double energy_left_for_env = energy_available - ruptures * energy_cost_rup;
		
		double envenoms = energy_left_for_env / energy_cost_env;
		double envenom_interval = rota_duration / envenoms;
		double env_buff_length = 1 + avg_cps_per_finisher;
		if(stats.getGearBuffs().hasSet("t15_2pc"))
			env_buff_length += 1;
		
		double time_left_for_env = rota_duration - ruptures * energy_cost_rup / energy_regen;
		double env_build_up = energy_cost_env / energy_regen;
		
		System.out.println(" ------ ");
		System.out.println("rota_duration " + rota_duration);
		System.out.println("time_left_for_env " + time_left_for_env);
		System.out.println("envenoms " + envenoms);
		System.out.println("envenom_interval " + envenom_interval);
		System.out.println("env_build_up " + env_build_up);
		System.out.println("env_buff_length " + env_buff_length);
		
		double muti_per_cycle = muti_needed * (ruptures + envenoms);
		double bs_per_cycle = bs_needed * (ruptures + envenoms);
		
		env_buff_length = Math.min(env_buff_length, envenom_interval);
		System.out.println("env_buff_length_after_cap " + env_buff_length);
		
		double env_buff_duration = env_buff_length * envenoms;
		double can_wait = pooling_time(energy_regen, env_ec , 120.);
//		System.out.println("can_wait " + can_wait);
		
		props.put("muti_per_cycle", muti_per_cycle);
		props.put("dsp_per_cycle", bs_per_cycle);
		props.put("env_per_cycle", envenoms);
		props.put("env_buff_duration", env_buff_duration);
		props.put("finisher_with_4cp", finisher_with_4cp);
		props.put("finisher_with_5cp", finisher_with_5cp);
		return props;
	}
	
	private HashMap<String, Double> assa_rotation_execute(double duration, boolean shadow_blades,double energy_regen, double extra_energy_from_pooling){
		HashMap<String,Double> props = new HashMap<String,Double>();
		
		double rota_duration = duration;
		
		double energy_available = energy_regen * rota_duration + extra_energy_from_pooling;
		
		// seal fate
		double dp_crit = attacks.get_crit_dispatch();
		
		double avg_cps_per_finisher = 0.0;
		double dsp_needed = 0.0;
		double finisher_with_4cp = 0.0;
		double finisher_with_5cp = 0.0;
		double active_t15_4pc = 0.;
		if(shadow_blades){
			active_t15_4pc = 1.;
			// test:
//			HashMap<Integer,Double> cp_per_cpg = this.get_cp_per_cpg(Arrays.asList(2.,dp_crit));
//			List<Distribution> bla = get_cp_distribution_for_cycle(cp_per_cpg ,5);
//			for(Distribution d : bla){
//				System.out.println(d.cps + ", " + d.moves + ", " + d.prob);
//			}
			
			double dp0_2_4 = (1-dp_crit)*(1-dp_crit);
			double dp0_2_5 = (1-dp_crit)*dp_crit;
			double dp0_3_5 = dp_crit*1;
			
			//double sum = dp0_2_4 + dp0_2_5 + dp0_3_5;
			
			dsp_needed = 3 * dp0_2_4 + 2 * dp0_2_5 + 2 * dp0_3_5;
			
			// how many finisher ends with 4 and 5 cps
			finisher_with_4cp = dp0_2_4;
			finisher_with_5cp = 1 - finisher_with_4cp;
			avg_cps_per_finisher = 4 * finisher_with_4cp + 5 * finisher_with_5cp;
		}else{
			double dp0_1_2_3_4 = (1-dp_crit)*(1-dp_crit)*(1-dp_crit)*(1-dp_crit);
			double dp0_1_2_3_5 = (1-dp_crit)*(1-dp_crit)*(1-dp_crit)*dp_crit;
			double dp0_1_2_4 = (1-dp_crit)*(1-dp_crit)*dp_crit;
			double dp0_1_3_4 = (1-dp_crit)*dp_crit*(1-dp_crit);
			double dp0_1_3_5 = (1-dp_crit)*dp_crit*dp_crit;
			
			double dp0_2_3_4 = dp_crit*(1-dp_crit)*(1-dp_crit);
			double dp0_2_4 = dp_crit*dp_crit;
			double dp0_2_3_5 = dp_crit*(1-dp_crit)*dp_crit;
			
			//double sum = dp0_1_2_3_4 + dp0_1_2_3_5 + dp0_1_2_4 + dp0_1_3_4 + dp0_1_3_5;
			//sum += dp0_2_3_4 + dp0_2_4 + dp0_2_3_5;
			
			dsp_needed = 4 * dp0_1_2_3_4 + 4 * dp0_1_2_3_5 + 3 * dp0_1_2_4;
			dsp_needed += 3 * dp0_1_3_4 + 3 * dp0_1_3_5;
			dsp_needed += 3 * dp0_2_3_4 + 2 * dp0_2_4 + 3 * dp0_2_3_5;
			
			// how many finisher ends with 4 and 5 cps
			finisher_with_4cp = dp0_1_2_3_4 + dp0_1_2_4 + dp0_1_3_4 + dp0_2_3_4 + dp0_2_4;
			finisher_with_5cp = 1 - finisher_with_4cp;
			avg_cps_per_finisher = 4 * finisher_with_4cp + 5 * finisher_with_5cp;
		}
		double dsp_ec = (30 - active_t15_4pc * get_t15_4pc(30)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double env_ec = (35 - active_t15_4pc * get_t15_4pc(35)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double rup_ec = (25 - active_t15_4pc * get_t15_4pc(25)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		
		double avg_rupture_length = 4 + 4 * avg_cps_per_finisher;
		if(stats.getGearBuffs().hasSet("t15_2pc"))
			avg_rupture_length += 4.;
		double avg_relentless_strikes = 5 * avg_cps_per_finisher;
		
		double energy_cost = dsp_needed * dsp_ec;
		double energy_cost_rup = energy_cost + rup_ec - avg_relentless_strikes;
		double energy_cost_env = energy_cost + env_ec - avg_relentless_strikes;
		
		double ruptures = rota_duration / avg_rupture_length;
		double energy_left_for_env = energy_available - ruptures * energy_cost_rup;
				
		double envenoms = energy_left_for_env / energy_cost_env;
		
		double dsp_per_cycle = dsp_needed * (ruptures + envenoms);
		
		double env_buff_length = 1 + avg_cps_per_finisher;
		if(stats.getGearBuffs().hasSet("t15_2pc"))
			env_buff_length += 1;
		double envenom_interval = rota_duration / envenoms;
		env_buff_length = Math.min(env_buff_length, envenom_interval);
		double env_buff_duration = env_buff_length * envenoms;
		
		props.put("dsp_per_cycle", dsp_per_cycle);
		props.put("env_per_cycle", envenoms);
		props.put("env_buff_duration", env_buff_duration);
		props.put("finisher_with_4cp", finisher_with_4cp);
		props.put("finisher_with_5cp", finisher_with_5cp);
		return props;
	}
	
	private HashMap<String, Double> assa_rotation_anticipation(double duration, boolean shadow_blades,double energy_regen, double extra_energy_from_pooling){
		HashMap<String,Double> props = new HashMap<String,Double>();
		
		double rota_duration = duration;
		double sb_extra_cp = 0;
		if(shadow_blades)
			sb_extra_cp = 1.;
		double muti_ec = (55 - sb_extra_cp * get_t15_4pc(55)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double env_ec = (35 - sb_extra_cp * get_t15_4pc(35)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double rup_ec = (25 - sb_extra_cp * get_t15_4pc(25)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		
		double energy_available = energy_regen * rota_duration + extra_energy_from_pooling;
		
		// seal fate
		double crit_chance_mh = attacks.get_crit_mutilate_mh();
		double crit_chance_oh = attacks.get_crit_mutilate_oh();
		double mut_crit = 1 - ( 1 - crit_chance_mh) * ( 1 - crit_chance_oh);
		double dp_crit = attacks.get_crit_dispatch();
		
		double blindside_proc_chance = 0.3 * stats.getMhStyleHitChance();

		HashMap<Integer,Double> cp_per_cpg = this.get_cp_per_cpg(Arrays.asList(1.,mut_crit,sb_extra_cp));
		double avg_cp_per_cpg = get_avg_cp_per_cpg(cp_per_cpg);
		HashMap<Integer,Double> dsp_cp_per_cpg = this.get_cp_per_cpg(Arrays.asList(dp_crit,sb_extra_cp));
		double dsp_avg_cp_per_cpg = get_avg_cp_per_cpg(dsp_cp_per_cpg);
		double extra_cp_through_blindside = blindside_proc_chance * dsp_avg_cp_per_cpg;
		
		double avg_cp_per_mutilate = avg_cp_per_cpg + extra_cp_through_blindside;
		
		// with anticipation
		double avg_cps_per_finisher = 5;
		double avg_rupture_length = 4 + 4 * avg_cps_per_finisher;
		if(stats.getGearBuffs().hasSet("t15_2pc"))
			avg_rupture_length += 4.;
		double avg_relentless_strikes = 5 * avg_cps_per_finisher;
		
		double muti_needed = avg_cps_per_finisher / avg_cp_per_mutilate;
		double dispatch_hits = muti_needed * blindside_proc_chance;
		
		double energy_cost = muti_needed * muti_ec;
		double energy_cost_rup = energy_cost + rup_ec - avg_relentless_strikes;
		double energy_cost_env = energy_cost + env_ec - avg_relentless_strikes;
		
		double ruptures = rota_duration / avg_rupture_length;
		double energy_left_for_env = energy_available - ruptures * energy_cost_rup;
				
		double envenoms = energy_left_for_env / energy_cost_env;
		
		double muti_per_cycle = muti_needed * (ruptures + envenoms);
		double bs_per_cycle = dispatch_hits * (ruptures + envenoms);
		
		double env_buff_length = 1 + avg_cps_per_finisher;
		if(stats.getGearBuffs().hasSet("t15_2pc"))
			env_buff_length += 1;
		double envenom_interval = rota_duration / envenoms;
		env_buff_length = Math.min(env_buff_length, envenom_interval);
		double env_buff_duration = env_buff_length * envenoms;
//		System.out.println("rota_duration " + rota_duration);
//		System.out.println("env_buff_duration " + env_buff_duration);
		
		props.put("muti_per_cycle", muti_per_cycle);
		props.put("dsp_per_cycle", bs_per_cycle);
		props.put("env_per_cycle", envenoms);
		props.put("env_buff_duration", env_buff_duration);
		props.put("finisher_with_4cp", 0.0);
		props.put("finisher_with_5cp", 1.0);
		return props;
	}
	
	private HashMap<String, Double> assa_rotation_execute_anticipation(double duration, boolean shadow_blades,double energy_regen, double extra_energy_from_pooling){
		HashMap<String,Double> props = new HashMap<String,Double>();
		
		double rota_duration = duration;
		double sb_extra_cp = 0;
		if(shadow_blades){
			sb_extra_cp = 1.;
		}
		double dsp_ec = (30 - sb_extra_cp * get_t15_4pc(30)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double env_ec = (35 - sb_extra_cp * get_t15_4pc(35)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double rup_ec = (25 - sb_extra_cp * get_t15_4pc(25)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		
		double energy_available = energy_regen * rota_duration + extra_energy_from_pooling;
		
		// seal fate
		double dp_crit = attacks.get_crit_dispatch();
		
		HashMap<Integer,Double> dsp_cp_per_cpg = this.get_cp_per_cpg(Arrays.asList(dp_crit,sb_extra_cp));
		double dsp_avg_cp_per_cpg = get_avg_cp_per_cpg(dsp_cp_per_cpg);
		
		double avg_cp_per_dsp = dsp_avg_cp_per_cpg;
		// with anticipation
		double avg_cps_per_finisher = 5;
		double avg_rupture_length = 4 + 4 * avg_cps_per_finisher;
		if(stats.getGearBuffs().hasSet("t15_2pc"))
			avg_rupture_length += 4.;
		double avg_relentless_strikes = 5 * avg_cps_per_finisher;
		
		double dsp_needed = avg_cps_per_finisher / avg_cp_per_dsp;
		
		double energy_cost = dsp_needed * dsp_ec;
		double energy_cost_rup = energy_cost + rup_ec - avg_relentless_strikes;
		double energy_cost_env = energy_cost + env_ec - avg_relentless_strikes;
		
		double ruptures = rota_duration / avg_rupture_length;
		double energy_left_for_env = energy_available - ruptures * energy_cost_rup;
				
		double envenoms = energy_left_for_env / energy_cost_env;
		
		double dsp_per_cycle = dsp_needed * (ruptures + envenoms);
		
		double env_buff_length = 1 + avg_cps_per_finisher;
		if(stats.getGearBuffs().hasSet("t15_2pc"))
			env_buff_length += 1;
		double envenom_interval = rota_duration / envenoms;
		env_buff_length = Math.min(env_buff_length, envenom_interval);
		double env_buff_duration = env_buff_length * envenoms;
		
		props.put("dsp_per_cycle", dsp_per_cycle);
		props.put("env_per_cycle", envenoms);
		props.put("env_buff_duration", env_buff_duration);
		props.put("finisher_with_4cp", 0.0);
		props.put("finisher_with_5cp", 1.0);
		return props;
	}
	
	@SuppressWarnings("unchecked")
	private void calc_dps_assassination3() {
		double sb_duration = get_shadow_blades_duration();
		
		while(true){
			double energy_regen = get_energy_regen();
			double pooling_time = 0;
			if(stats.getGearBuffs().has("assa_pool_before_sb"))
				pooling_time = pooling_time(energy_regen, 120);
			
			HashMap<String,Double> assa_non_execute_sb = new HashMap<String,Double>();
			HashMap<String,Double> assa_non_execute_normal = new HashMap<String,Double>();
			HashMap<String,Double> assa_execute_sb = new HashMap<String,Double>();
			HashMap<String,Double> assa_execute_normal = new HashMap<String,Double>();
			HashMap<String,Double> assa_non_execute = new HashMap<String,Double>();
			HashMap<String,Double> assa_execute = new HashMap<String,Double>();
			if(stats.hasTalent("anticipation")){
				// for shadow_blades up
				assa_non_execute_sb = assa_rotation_anticipation(sb_duration, true, energy_regen, pooling_time * energy_regen);
				// for shadow_blades down
				assa_non_execute_normal = assa_rotation_anticipation(sb_actual_cd-sb_duration, false, energy_regen, pooling_time * -energy_regen);

				assa_non_execute = combineProps(assa_non_execute_normal,assa_non_execute_sb);
				
				// dispatch rotation < 35%
				// for shadow_blades up
				assa_execute_sb = assa_rotation_execute_anticipation(sb_duration, true, energy_regen, pooling_time * energy_regen);
				// for shadow_blades down
				assa_execute_normal = assa_rotation_execute_anticipation(sb_actual_cd-sb_duration, false, energy_regen, pooling_time * -energy_regen);

				assa_execute = combineProps(assa_execute_normal,assa_execute_sb);
			}else{
				// for shadow_blades up
				assa_non_execute_sb = assa_rotation(12., true, energy_regen, pooling_time * energy_regen);
				// for shadow_blades down
				assa_non_execute_normal = assa_rotation((sb_actual_cd+pooling_time)-12., false, energy_regen, pooling_time * -energy_regen);
				
				assa_non_execute = combineProps(assa_non_execute_normal,assa_non_execute_sb);
				
				// dispatch rotation < 35%
				// for shadow_blades up
				assa_execute_sb = assa_rotation_execute(12., true, energy_regen, pooling_time * energy_regen);
				// for shadow_blades down
				assa_execute_normal = assa_rotation_execute((sb_actual_cd+pooling_time)-12., false, energy_regen, pooling_time * -energy_regen);

				assa_execute = combineProps(assa_execute_normal,assa_execute_sb);
			}
			
			//
			double execute_time = 0.35;
			
			double muti_per_sec = assa_non_execute.get("muti_per_cycle") / sb_actual_cd;
			double bs_per_sec = assa_non_execute.get("dsp_per_cycle") / sb_actual_cd;
			double env_per_sec = assa_non_execute.get("env_per_cycle") / sb_actual_cd;
			double dsp_per_sec = assa_execute.get("dsp_per_cycle") / sb_actual_cd;
			double dsp_env_per_sec = assa_execute.get("env_per_cycle") / sb_actual_cd;
			
			double env_buff_uptime = (1-execute_time) * assa_non_execute.get("env_buff_duration") + execute_time * assa_execute.get("env_buff_duration");
			env_buff_uptime /= sb_actual_cd;
			
			
			double finisher_with_4cp_non_execute = sb_duration * assa_non_execute_sb.get("finisher_with_4cp") + (sb_actual_cd-sb_duration) * assa_non_execute_normal.get("finisher_with_4cp");
			finisher_with_4cp_non_execute /= sb_actual_cd;
			double finisher_with_4cp_execute = sb_duration * assa_execute_sb.get("finisher_with_4cp") + (sb_actual_cd-sb_duration) * assa_execute_normal.get("finisher_with_4cp");
			finisher_with_4cp_execute /= sb_actual_cd;
			
			double finisher_with_5cp_non_execute = sb_duration * assa_non_execute_sb.get("finisher_with_5cp") + (sb_actual_cd-sb_duration) * assa_non_execute_normal.get("finisher_with_5cp");
			finisher_with_5cp_non_execute /= sb_actual_cd;
			double finisher_with_5cp_execute = sb_duration * assa_execute_sb.get("finisher_with_5cp") + (sb_actual_cd-sb_duration) * assa_execute_normal.get("finisher_with_5cp");
			finisher_with_5cp_execute /= sb_actual_cd;
			
			finisher_with_4cp = (1-execute_time) * finisher_with_4cp_non_execute + execute_time * finisher_with_4cp_execute;
			finisher_with_5cp = (1-execute_time) * finisher_with_5cp_non_execute + execute_time * finisher_with_5cp_execute;
			aps.put("mutilate_mh", (1-execute_time) * muti_per_sec);
			aps.put("mutilate_oh", (1-execute_time) * muti_per_sec);
			aps.put("dispatch", (1-execute_time) * bs_per_sec + execute_time * dsp_per_sec);
			aps.put("envenom", (1-execute_time) * env_per_sec + execute_time * dsp_env_per_sec);
			
			if(stats.hasTalent("marked_for_death")){
				double extra_env_per_sec = 1. / 60.;
				aps.put("envenom", aps.get("envenom") + extra_env_per_sec);
			}
			
			double sb_uptime = sb_duration / sb_actual_cd;
			this.white_attacks(sb_uptime);
			
			double total_mh_hits = aps.get("mh") + aps.get("mutilate_mh") + aps.get("dispatch") + aps.get("envenom") + aps.get("sb_mh");
			double total_oh_hits = aps.get("oh") + aps.get("mutilate_oh") + aps.get("sb_oh");
			
			if(Math.abs(total_hits - (total_mh_hits + total_oh_hits)) < PRECISION)
				break;
			
			aps.put("venomous_wound", 0.5 * 0.75 * stats.getMhPoisonHitChance() );
			aps.put("rupture", 1./2);
			total_hits = aps.get("mh") + aps.get("oh") + aps.get("envenom") + aps.get("sb_mh") + aps.get("sb_oh");
			total_hits += aps.get("mutilate_mh") + aps.get("mutilate_oh");
			total_hits += aps.get("dispatch");
			
			double poison_extra_proc_chance = env_buff_uptime * 0.15;
			double ip_per_sec = total_mh_hits * (0.3+0.2+poison_extra_proc_chance) * stats.getMhPoisonHitChance();
			ip_per_sec += total_oh_hits * (0.3+0.2+poison_extra_proc_chance) * stats.getOhPoisonHitChance();
			aps.put("instant_poison", ip_per_sec);
			aps.put("deadly_poison", 1./3);
			
			this.procs(total_mh_hits, total_oh_hits, ip_per_sec);
		}
	}
	
	@SuppressWarnings("unused")
	private void calc_dps_assassination2() {
		double muti_ec = 55 * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double dp_ec = 30 * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double env_ec = 35 * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double rup_ec = 25 * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		
		while(true){
			double sb_duration = get_shadow_blades_duration();
			double sb_uptime = sb_duration / sb_actual_cd;
			
			double crit_chance_mh = attacks.get_crit_mutilate_mh();
			double crit_chance_oh = attacks.get_crit_mutilate_oh();
			
			double mut_crit = 1 - ( 1 - crit_chance_mh) * ( 1 - crit_chance_oh);
			double dp_crit = attacks.get_crit_dispatch();
			
			double avg_cp_per_mutilate = 2 + mut_crit + sb_uptime + 0.3 * (1 + dp_crit + sb_uptime) * stats.getMhStyleHitChance();
			System.out.println("muti avg_cp_per_mutilate " + avg_cp_per_mutilate);
			
			double avg_cps_per_finisher = 5;
			
			double avg_rupture_length = 4 + 4 * avg_cps_per_finisher;
			
			double avg_rs = 5 * avg_cps_per_finisher;
			
			double muti_needed = avg_cps_per_finisher / avg_cp_per_mutilate;
			double dispatch_hits = muti_needed * 0.3 * stats.getMhStyleHitChance();;
			
			double energy_cost = muti_needed * muti_ec;
			double energy_cost_rup = energy_cost + rup_ec - avg_rs;
			double energy_cost_env = energy_cost + env_ec - avg_rs;
			
			double energy_regen = get_energy_regen();
			double build_time_rup = energy_cost_rup / energy_regen;
			double build_time_env = energy_cost_env / energy_regen;
			
			double cycle_time_left_for_env = avg_rupture_length - build_time_rup;
			double env_per_cycle = cycle_time_left_for_env / build_time_env;
			
			double muti_per_cycle = muti_needed * (1 + env_per_cycle);
			double bs_per_cycle = dispatch_hits * (1 + env_per_cycle);
			
			double muti_per_sec = muti_per_cycle / avg_rupture_length;
			double bs_per_sec = bs_per_cycle / avg_rupture_length;
			double env_per_sec = env_per_cycle / avg_rupture_length;
			
			// dispatch rota < 35%
			double avg_cp_per_style = 1 + dp_crit + sb_uptime;
			System.out.println("avg_cp_per_style " + avg_cp_per_style);
			double dp_finisher_cps = 5;
			double dp_per_finisher = dp_finisher_cps / avg_cp_per_style;
			double dp_avg_rupture_length = 4 + dp_finisher_cps * 4;
			
			double dp_avg_rs = dp_finisher_cps * 5;
			
			double dp_energy_cost = dp_per_finisher * dp_ec;
			double dp_energy_cost_rup = dp_energy_cost + rup_ec - dp_avg_rs;
			double dp_energy_cost_env = dp_energy_cost + env_ec - dp_avg_rs;
			
			double dp_build_time_rup = dp_energy_cost_rup / energy_regen;
			double dp_build_time_env = dp_energy_cost_env / energy_regen;
			
			double dp_cycle_time_left_for_env = dp_avg_rupture_length - dp_build_time_rup;
			double dp_env_per_cycle = dp_cycle_time_left_for_env / dp_build_time_env;
			
			double dp_per_cycle = dp_per_finisher * (1 + dp_env_per_cycle);
			
			double dp_per_sec = dp_per_cycle / dp_avg_rupture_length;
			double dp_env_per_sec = dp_env_per_cycle / dp_avg_rupture_length;
			
			//
			double execute_time = 0.35;
			
			aps.put("mutilate_mh", (1-execute_time) * muti_per_sec);
			aps.put("mutilate_oh", (1-execute_time) * muti_per_sec);
			aps.put("dispatch", (1-execute_time) * bs_per_sec + execute_time * dp_per_sec);
			aps.put("envenom", (1-execute_time) * env_per_sec + execute_time * dp_env_per_sec);
						
			this.white_attacks(sb_uptime);
			
			double total_mh_hits = aps.get("mh") + aps.get("mutilate_mh") + aps.get("dispatch") + aps.get("envenom") + aps.get("sb_mh");
			double total_oh_hits = aps.get("oh") + aps.get("mutilate_oh") + aps.get("sb_oh");
			
			if(Math.abs(total_hits - (total_mh_hits + total_oh_hits)) < PRECISION)
				break;
			
			aps.put("venomous_wound", 0.5 * 0.75 * stats.getMhPoisonHitChance() );
			aps.put("rupture", 1./2);
			total_hits = aps.get("mh") + aps.get("oh") + aps.get("envenom") + aps.get("sb_mh") + aps.get("sb_oh");
			total_hits += aps.get("mutilate_mh") + aps.get("mutilate_oh");
			total_hits += aps.get("dispatch");
			
			// envenom buff uptime...
			double env_buff_length = 1 + (1-execute_time) * avg_cps_per_finisher + execute_time * dp_finisher_cps;
			// we try not to clip which should work because we include rupture in our rotation
			// and performing good energy pooling
			double env_buff_uptime = env_buff_length * aps.get("envenom");
			double poison_extra_proc_chance = env_buff_uptime * 0.15;
			double ip_per_sec = total_mh_hits * (0.3+0.2+poison_extra_proc_chance) * stats.getMhPoisonHitChance();
			ip_per_sec += total_oh_hits * (0.3+0.2+poison_extra_proc_chance) * stats.getOhPoisonHitChance();
			aps.put("instant_poison", ip_per_sec);
			aps.put("deadly_poison", 1./3);
			
			this.procs(total_mh_hits, total_oh_hits, ip_per_sec);
		}
	}

	@SuppressWarnings("unused")
	private void calc_dps_assassination() {
		double muti_ec = 55 * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double dp_ec = 30 * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double env_ec = 35 * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double rup_ec = 25 * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		
		while(true){
			double sb_duration = get_shadow_blades_duration();
			double sb_uptime = sb_duration / sb_actual_cd;
						
			double crit_chance_mh = attacks.get_crit_mutilate_mh();
			double crit_chance_oh = attacks.get_crit_mutilate_oh();
				
			double all_crit = 1 - ( 1 - crit_chance_mh) * ( 1 - crit_chance_oh);
			double dp_crit = attacks.get_crit_dispatch();
			
			//double avg_cp_per_mutilate = 2 + all_crit;
			
			// shadow blades
			double sb_mut0_3_5 = 0.7 * (1-all_crit)*0.7*1;
			double sb_mut0_3_5dp = 0.7 *(1-all_crit)*0.3*1;
			double sb_mut0_4 = 0.7 *all_crit*0.7;
			double sb_mut0_4_5dp = 0.7*all_crit*0.3*1;
			// is it better to finish with 4 and use the free dp in next rota???
			
			double sb_mut0_3_5dp2 = 0.3*dp_crit*1;
			double sb_mut0_2_5 = 0.3*(1-dp_crit)*1;
			
			double sb_muti_needed = 2 * sb_mut0_3_5 + 1 * sb_mut0_3_5dp + 1 * sb_mut0_4;
			sb_muti_needed += 1 * sb_mut0_4_5dp + 1 * sb_mut0_3_5dp2 + 1 * sb_mut0_2_5;
			
			double sb_bs_needed = 1 * sb_mut0_3_5dp + 1 * sb_mut0_4_5dp + 1 * sb_mut0_3_5dp2 + 1 * sb_mut0_2_5;
			
			double sb_finisher_with_4cp = sb_mut0_4;
			double sb_finisher_with_5cp = 1 - sb_finisher_with_4cp;
			double sb_avg_cps_per_finisher = 4 * sb_finisher_with_4cp + 5 * sb_finisher_with_5cp;
			
			double mut0_2_4 = 0.7*(1-all_crit)*0.7*(1-all_crit)*0.7;//ok
			double mut0_2_4dp = 0.7*(1-all_crit)*0.3*dp_crit;//ok
			double mut0_2_3_5 = 0.7*(1-all_crit)*0.3*(1-dp_crit)*1;//ok
			double mut0_2_4_5 = 0.7*(1-all_crit)*0.7*(1-all_crit) * 0.3 * 1;//ok
			double mut0_2_5 = 0.7*(1-all_crit)*0.7*all_crit;//ok
			
			double mut0_3_5 = 0.7 * all_crit * 0.7 * 1;//ok
			double mut0_3_5dp = 0.7 * all_crit * 0.3 * dp_crit;//ok
			double mut0_3_4 = 0.7 * all_crit*0.3*(1-all_crit);//ok
	
			double mut0_1_3_5 = 0.3 * (1-dp_crit) * (1-all_crit)*0.7*1;//ok
			double mut0_1_3_4 = 0.3 * (1-dp_crit) * (1-all_crit)*0.3 * (1-dp_crit);
			double mut0_1_3_5dp = 0.3 * (1-dp_crit) * (1-all_crit)*0.3 * dp_crit;
			double mut0_1_4 = 0.3 * (1-dp_crit) * all_crit*0.7;
			double mut0_1_4_5 = 0.3 * (1-dp_crit) * all_crit * 0.3;
			double mut0_2_4dp2 = 0.3 * dp_crit * (1-all_crit) * 0.7;
			double mut0_2_4_5dp2 = 0.3 * dp_crit * (1-all_crit) * 0.3 * 1;
			double mut0_2_5dp2 = 0.3 * dp_crit * all_crit;
			
//			double sum1 = mut0_2_4 + mut0_2_4dp + mut0_2_3_5 + mut0_2_4_5 + mut0_2_5 + mut0_3_5 + mut0_3_5dp + mut0_3_4;
//			System.out.println("checksum1: " + sum1);
//			double sum2 = mut0_1_3_5 + mut0_1_3_4 + mut0_1_4 + mut0_1_4_5 + mut0_2_4dp2 + mut0_2_4_5dp2 + mut0_2_5dp2 + mut0_1_3_5dp;
//			System.out.println("checksum2: " + sum2);
//			System.out.println("checksum: " + (sum1+sum2));
//			System.out.println("checksum_inv " + (1-(sum1+sum2)));
			
			double muti_needed = 2 * mut0_2_4 + 2 * mut0_2_5 + 2 * mut0_3_5;
			muti_needed += 2 * mut0_1_3_5 + 1 * mut0_1_3_4 + 1 * mut0_1_3_4;
			muti_needed += 1 * mut0_1_4 + 1 * mut0_1_4_5 + 2 * mut0_2_3_5;
			muti_needed += 2 * mut0_2_4_5 + 1 * mut0_3_4 + 1 * mut0_2_4dp;
			muti_needed += 1 * mut0_3_5dp + 1 * mut0_1_3_5dp + 1 * mut0_2_4dp2;
			muti_needed += 1 * mut0_2_4_5dp2 + 1 * mut0_2_5dp2;
			
			double bs_needed = 1 * mut0_1_3_5 + 2 * mut0_1_3_4 + 1 * mut0_1_4;
			bs_needed += 2 * mut0_1_4_5 + 1 * mut0_2_3_5 + 1 * mut0_2_4_5;
			bs_needed += 1 * mut0_3_4 + 1 * mut0_2_4dp + 1 * mut0_3_5dp;
			bs_needed += 2 * mut0_1_3_5dp + 1 * mut0_2_4dp2 + 2 * mut0_2_4_5dp2;
			bs_needed += 1 * mut0_2_5dp2;
				
			// how many finisher ends with 4 and 5 cps
			double finisher_with_4cp = mut0_2_4 + mut0_2_4dp + mut0_1_3_4 + mut0_1_4 + mut0_2_4dp2 + mut0_3_4;
			double finisher_with_5cp = 1 - finisher_with_4cp;
			double avg_cps_per_finisher = 4 * finisher_with_4cp + 5 * finisher_with_5cp;
			
			// merge shadowblades and normal
			avg_cps_per_finisher = sb_uptime * sb_avg_cps_per_finisher + (1-sb_uptime) * avg_cps_per_finisher;
			muti_needed = sb_uptime * sb_muti_needed + (1-sb_uptime) * muti_needed;
			bs_needed = sb_uptime * sb_bs_needed + (1-sb_uptime) * bs_needed;
			
			double avg_rupture_length = 4 + 4 * avg_cps_per_finisher;
			
			double avg_rs = 5 * avg_cps_per_finisher;
			
			double energy_cost = muti_needed * muti_ec + bs_needed * 0;
			double energy_cost_rup = energy_cost + rup_ec - avg_rs;
			double energy_cost_env = energy_cost + env_ec - avg_rs;
			
			double energy_regen = get_energy_regen();
			double build_time_rup = energy_cost_rup / energy_regen;
			double build_time_env = energy_cost_env / energy_regen;
			
			double cycle_time_left_for_env = avg_rupture_length - build_time_rup;
			double env_per_cycle = cycle_time_left_for_env / build_time_env;
			
			double muti_per_cycle = muti_needed * (1 + env_per_cycle);
			double bs_per_cycle = bs_needed * (1 + env_per_cycle);
			
			double muti_per_sec = muti_per_cycle / avg_rupture_length;
			double bs_per_sec = bs_per_cycle / avg_rupture_length;
			double env_per_sec = env_per_cycle / avg_rupture_length;
			
			// dispatch rota < 35%
			double avg_cp_per_style = 1 + attacks.get_crit_dispatch();
			double sb_avg_cp_per_style = 2 + attacks.get_crit_dispatch();
			avg_cp_per_style = sb_uptime * sb_avg_cp_per_style + (1-sb_uptime) * avg_cp_per_style;
			double dp_finisher_cps = 5;
			double dp_per_finisher = dp_finisher_cps / avg_cp_per_style;
			double dp_avg_rupture_length = 4 + dp_finisher_cps * 4;
			
			double dp_avg_rs = dp_finisher_cps * 5;
			
			double dp_energy_cost = dp_per_finisher * dp_ec;
			double dp_energy_cost_rup = dp_energy_cost + rup_ec - dp_avg_rs;
			double dp_energy_cost_env = dp_energy_cost + env_ec - dp_avg_rs;
			
			double dp_build_time_rup = dp_energy_cost_rup / energy_regen;
			double dp_build_time_env = dp_energy_cost_env / energy_regen;
			
			double dp_cycle_time_left_for_env = dp_avg_rupture_length - dp_build_time_rup;
			double dp_env_per_cycle = dp_cycle_time_left_for_env / dp_build_time_env;
			
			double dp_per_cycle = dp_per_finisher * (1 + dp_env_per_cycle);
			
			double dp_per_sec = dp_per_cycle / dp_avg_rupture_length;
			double dp_env_per_sec = dp_env_per_cycle / dp_avg_rupture_length;
			
			
			double execute_time = 0.35;
			
			aps.put("mutilate_mh", (1-execute_time) * muti_per_sec);
			aps.put("mutilate_oh", (1-execute_time) * muti_per_sec);
			aps.put("dispatch", (1-execute_time) * bs_per_sec + execute_time * dp_per_sec);
			aps.put("envenom", (1-execute_time) * env_per_sec + execute_time * dp_env_per_sec);
						
			this.white_attacks(sb_uptime);
			
			double total_mh_hits = aps.get("mh") + aps.get("mutilate_mh") + aps.get("dispatch") + aps.get("envenom") + aps.get("sb_mh");
			double total_oh_hits = aps.get("oh") + aps.get("mutilate_oh") + aps.get("sb_oh");
			
			if(Math.abs(total_hits - (total_mh_hits + total_oh_hits)) < PRECISION)
				break;
			
			aps.put("venomous_wound", 0.5 * 0.75 * stats.getMhPoisonHitChance() );
			aps.put("deadly_poison", 1./3);
			aps.put("rupture", 1./2);
			total_hits = aps.get("mh") + aps.get("oh") + aps.get("envenom") + aps.get("sb_mh") + aps.get("sb_oh");
			total_hits += aps.get("mutilate_mh") + aps.get("mutilate_oh");
			total_hits += aps.get("dispatch");
			
			// envenom buff uptime...
			double env_buff_length = 1 + (1-execute_time) * avg_cps_per_finisher + execute_time * dp_finisher_cps;
			// we try not to clip which should work because we include rupture in our rotation
			// and performing good energy pooling
			double env_buff_uptime = env_buff_length * ((1-execute_time)*env_per_cycle+execute_time*dp_env_per_cycle) / ((1-execute_time)*avg_rupture_length+execute_time*dp_avg_rupture_length);
			double poison_extra_proc_chance = env_buff_uptime * 0.15;
			double ip_per_sec = total_mh_hits * (0.3+0.2+poison_extra_proc_chance) * stats.getMhPoisonHitChance();
			ip_per_sec += total_oh_hits * (0.3+0.2+poison_extra_proc_chance) * stats.getOhPoisonHitChance();
			aps.put("instant_poison", ip_per_sec);
			
			this.procs(total_mh_hits,total_oh_hits, ip_per_sec);
		}
	}
	
//	private HashMap<String, Double> combat_rotation_anticipation(double duration, boolean shadow_blades,double energy_regen, double flat_extra_energy, boolean ar_active){
//		HashMap<String,Double> props = new HashMap<String,Double>();
//		
//		double rota_duration = duration;
//		double energy_available = energy_regen * rota_duration + flat_extra_energy;
//		
//		double extra_cp = 0.2;
//		double avg_cps_per_finisher = 5.0;
//		double ss_needed = 0.0;
//		double finisher_with_4cp = 0.0;
//		double finisher_with_5cp = 1.0;
//		double active_t15_4pc = 0.;
//		if(shadow_blades){
//			active_t15_4pc = 1.;
//			
//			double cp_per_ss = 1. + extra_cp + 1.;
//			ss_needed = avg_cps_per_finisher / cp_per_ss;
//		} else {
//			double cp_per_ss = 1. + extra_cp;
//			ss_needed = avg_cps_per_finisher / cp_per_ss;
//		}
//		double ss_ec = (40 - active_t15_4pc * get_t15_4pc(40)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
//		double evi_ec = (35 - active_t15_4pc * get_t15_4pc(35)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
//		double rup_ec = (25 - active_t15_4pc * get_t15_4pc(25)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
//		double snd_ec = 25 - active_t15_4pc * get_t15_4pc(25);
//		
//		double avg_rupture_length = get_rupture_duration(avg_cps_per_finisher);
//		double avg_snd_length = get_snd_duration(avg_cps_per_finisher);
//		double avg_relentless_strikes = 5 * avg_cps_per_finisher;
//
//		double energy_cost = ss_needed * ss_ec;
//		double energy_cost_rup = energy_cost + rup_ec - avg_relentless_strikes;
//		double energy_cost_evi = energy_cost + evi_ec - avg_relentless_strikes;
//		double energy_cost_snd = energy_cost + snd_ec - avg_relentless_strikes;
//	
//		double ruptures = rota_duration / avg_rupture_length;
//		if(!stats.getGearBuffs().has("combat_use_rupture"))
//			ruptures = 0.0;
//		double snds = rota_duration / avg_snd_length;
//		double energy_left_for_evi = energy_available - ruptures * energy_cost_rup;
//		energy_left_for_evi -= snds * energy_cost_snd;
//		
//		double attacks_so_far = (ruptures + snds) * (1 + ss_needed);
//		double gcd = 1.0;
//		if(ar_active && stats.getGlyphs().contains("adrenaline_rush")){
//			gcd = 0.8;
//		}
//		double time_left = rota_duration - attacks_so_far * gcd;
//		double sum_attacks_per_evi = ss_needed + 1;
//		double evis_time_limited = time_left / (sum_attacks_per_evi * gcd);
//		double evis_energy_limited = energy_left_for_evi / energy_cost_evi;
//		
//		double evis = evis_energy_limited;
//		if(evis_time_limited < evis_energy_limited){
//			evis = evis_time_limited;
//			double energy_lost = energy_left_for_evi - evis * energy_cost_evi;
//			//View.addConsole("EP "+ stats.calcEP + " - energy lost due to capping: " + energy_lost);
//		}
//		
//		double ss_per_cycle = ss_needed * (snds + ruptures + evis);
//		
//		props.put("ss_per_cycle", ss_per_cycle);
//		props.put("evi_per_cycle", evis);
//		props.put("rup_per_cycle", ruptures);
//		props.put("energy_cost_evi", energy_cost_evi);
//		props.put("avg_cps_per_finisher", avg_cps_per_finisher);
//		props.put("ss_per_finisher", ss_needed);
//		props.put("finisher_with_4cp", finisher_with_4cp);
//		props.put("finisher_with_5cp", finisher_with_5cp);
//		return props;
//	}
//	
//	private HashMap<String, Double> combat_rotation(double duration, boolean shadow_blades,double energy_regen, double flat_extra_energy, boolean ar_active){
//		HashMap<String,Double> props = new HashMap<String,Double>();
//		
//		double rota_duration = duration;
//		double energy_available = energy_regen * rota_duration + flat_extra_energy;
//		
//		double extra_cp = 0.2;
//		double avg_cps_per_finisher = 0.0;
//		double ss_needed = 0.0;
//		double finisher_with_4cp = 0.0;
//		double finisher_with_5cp = 0.0;
//		double active_t15_4pc = 0.;
//		if(shadow_blades){
//			active_t15_4pc = 1.;
//			
//			double sb_ss0_2_4 = (1-extra_cp)*(1-extra_cp);
//			double sb_ss0_2_5 = (1-extra_cp)*extra_cp;
//			double sb_ss0_3_5 = extra_cp * 1;
//			
//			ss_needed = 2 * sb_ss0_2_4 + 2 * sb_ss0_2_5 + 1 * sb_ss0_3_5;
//			
//			finisher_with_4cp = sb_ss0_2_4;
//			finisher_with_5cp = 1 - finisher_with_4cp;
//			avg_cps_per_finisher = 4 + finisher_with_5cp;
//		} else {
//			double ss0_1_2_3_4 = (1-extra_cp)*(1-extra_cp)*(1-extra_cp)*(1-extra_cp); // 4
//			double ss0_1_2_3_5 = (1-extra_cp)*(1-extra_cp)*(1-extra_cp)*extra_cp; // 4
//			double ss0_1_2_4 = (1-extra_cp)*(1-extra_cp)*extra_cp; // 3
//			double ss0_1_3_4 = (1-extra_cp)*extra_cp*(1-extra_cp); // 3
//			double ss0_1_3_5 = (1-extra_cp)*extra_cp*extra_cp; // 3
//			double ss0_2_3_4 = extra_cp*(1-extra_cp)*(1-extra_cp); // 3
//			double ss0_2_3_5 = extra_cp*(1-extra_cp)*extra_cp; // 3
//			double ss0_2_4 = extra_cp*extra_cp; // 2
//			
//			ss_needed  = 4 * ss0_1_2_3_4 + 4 * ss0_1_2_3_5;
//			ss_needed += 3 * (ss0_1_2_4 + ss0_1_3_4 + ss0_1_3_5 + ss0_2_3_4 + ss0_2_3_5);
//			ss_needed += 2 * ss0_2_4;
//			
//			// how many finisher ends with 4 and 5 cps
//			finisher_with_4cp = ss0_1_2_3_4 + ss0_1_2_4 + ss0_1_3_4 + ss0_2_3_4 + ss0_2_4;
//			finisher_with_5cp = 1 - finisher_with_4cp;
//			avg_cps_per_finisher = 4 + finisher_with_5cp;
//			
//			// lets try out the case we are always using ss when we are at 4 cps
//			// so we will waste possible cps
//			if(stats.getGearBuffs().has("combat_waste_cp")){
//				ss_needed  = 5 * ss0_1_2_3_4 + 4 * ss0_1_2_3_5;
//				ss_needed += 3 * ss0_1_3_5 + 3 * ss0_2_3_5;
//				ss_needed += 4 * (ss0_1_2_4 + ss0_1_3_4 + ss0_2_3_4);
//				ss_needed += 3 * ss0_2_4;
//				
//				finisher_with_4cp = 0.0;
//				finisher_with_5cp = 1.0;
//				avg_cps_per_finisher = 5.0;
//			}
//		}
//		double ss_ec = (40 - active_t15_4pc * get_t15_4pc(40)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
//		double evi_ec = (35 - active_t15_4pc * get_t15_4pc(35)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
//		double rup_ec = (25 - active_t15_4pc * get_t15_4pc(25)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
//		double snd_ec = 25 - active_t15_4pc * get_t15_4pc(25);
//		
//		double avg_rupture_length = get_rupture_duration(avg_cps_per_finisher);
//		double avg_snd_length = get_snd_duration(avg_cps_per_finisher);
//		double avg_relentless_strikes = 5 * avg_cps_per_finisher;
//
//		double energy_cost = ss_needed * ss_ec;
//		double energy_cost_rup = energy_cost + rup_ec - avg_relentless_strikes;
//		double energy_cost_evi = energy_cost + evi_ec - avg_relentless_strikes;
//		double energy_cost_snd = energy_cost + snd_ec - avg_relentless_strikes;
//		
//		double ruptures = rota_duration / avg_rupture_length;
//		if(!stats.getGearBuffs().has("combat_use_rupture"))
//			ruptures = 0.;
//		double snds = rota_duration / avg_snd_length;
//		double energy_left_for_evi = energy_available - ruptures * energy_cost_rup;
//		energy_left_for_evi -= snds * energy_cost_snd;
//		
//		double attacks_so_far = (ruptures + snds) * (1 + ss_needed);
//		double gcd = 1.0;
//		if(ar_active && stats.getGlyphs().contains("adrenaline_rush")){
//			gcd = 0.8;
//		}
//		double time_left = rota_duration - attacks_so_far * gcd;
//		double sum_attacks_per_evi = ss_needed + 1;
//		double evis_time_limited = time_left / (sum_attacks_per_evi * gcd);
//		double evis_energy_limited = energy_left_for_evi / energy_cost_evi;
//		
//		double evis = evis_energy_limited;
//		if(evis_time_limited < evis_energy_limited){
//			evis = evis_time_limited;
//			double energy_lost = energy_left_for_evi - evis * energy_cost_evi;
//			//View.addConsole("EP "+ stats.calcEP + " - energy lost due to capping: " + energy_lost);
//		}
//		
//		double ss_per_cycle = ss_needed * (snds + ruptures + evis);
//		
//		props.put("ss_per_cycle", ss_per_cycle);
//		props.put("evi_per_cycle", evis);
//		props.put("rup_per_cycle", ruptures);
//		props.put("energy_cost_evi", energy_cost_evi);
//		props.put("avg_cps_per_finisher", avg_cps_per_finisher);
//		props.put("ss_per_finisher", ss_needed);
//		props.put("finisher_with_4cp", finisher_with_4cp);
//		props.put("finisher_with_5cp", finisher_with_5cp);
//		return props;
//	}
	
	@SuppressWarnings("unchecked")
	private HashMap<String, Double> combat_rota(double rota_duration, boolean shadow_blades, double ar_duration, boolean sync_ar_with_sb, double energy_regen, double flat_extra_energy){
		HashMap<String,Double> props = new HashMap<String,Double>();
		
		double ar_uptime = ar_duration / rota_duration;
		// TODO validate if this is correct for no sync
		// looks correct
		if(!sync_ar_with_sb)
			ar_uptime = 15. / sb_actual_cd;
		HashMap<String,Double> white_attacks = this.combat_white_attacks(shadow_blades, ar_uptime);
		
		double mh_hits = 0.0;
		double oh_hits = 0.0;
		if(shadow_blades){
			mh_hits = white_attacks.get("sb_mh");
			oh_hits = white_attacks.get("sb_oh");
		}else{
			mh_hits = white_attacks.get("mh");
			oh_hits = white_attacks.get("oh");
		}
		// lets find out how much energy we get from main_gauche
		double proc_chance_oh = stats.getOh().getSpeed()/1.4 * 0.2;
		double proc_chance_mh = 0.2;
		double proc_chance_mg = this.stats.getBuffed(Stat.mst) * 0.02 * stats.getMhStyleHitChance();
		double cp_regen = 15. * oh_hits * proc_chance_oh;
		cp_regen += mh_hits * proc_chance_mg * 15. * proc_chance_mh;
		double mg_regen = cp_regen;
		
		energy_regen += mg_regen;
		double energy_available = energy_regen * rota_duration + flat_extra_energy;

		double extra_cp = 0.2;
		double avg_cps_per_finisher = 0.0;
		double ss_needed = 0.0;
		double finisher_with_4cp = 0.0;
		double finisher_with_5cp = 0.0;
		double active_t15_4pc = 0.;
		if(stats.hasTalent("anticipation")){
			avg_cps_per_finisher = 5.0;
			if(shadow_blades){
				active_t15_4pc = 1.;
				
				double cp_per_ss = 1. + extra_cp + 1.;
				ss_needed = avg_cps_per_finisher / cp_per_ss;
			} else {
				double cp_per_ss = 1. + extra_cp;
				ss_needed = avg_cps_per_finisher / cp_per_ss;
			}
			finisher_with_4cp = 0.0;
			finisher_with_5cp = 1.0;
		}
		else {
			if(shadow_blades){
				active_t15_4pc = 1.;
				
				double sb_ss0_2_4 = (1-extra_cp)*(1-extra_cp);
				double sb_ss0_2_5 = (1-extra_cp)*extra_cp;
				double sb_ss0_3_5 = extra_cp * 1;
				
				ss_needed = 2 * sb_ss0_2_4 + 2 * sb_ss0_2_5 + 2 * sb_ss0_3_5;
				
				finisher_with_4cp = sb_ss0_2_4;
				finisher_with_5cp = 1 - finisher_with_4cp;
				avg_cps_per_finisher = 4 + finisher_with_5cp;
			} else {
				double ss0_1_2_3_4 = (1-extra_cp)*(1-extra_cp)*(1-extra_cp)*(1-extra_cp); // 4
				double ss0_1_2_3_5 = (1-extra_cp)*(1-extra_cp)*(1-extra_cp)*extra_cp; // 4
				double ss0_1_2_4 = (1-extra_cp)*(1-extra_cp)*extra_cp; // 3
				double ss0_1_3_4 = (1-extra_cp)*extra_cp*(1-extra_cp); // 3
				double ss0_1_3_5 = (1-extra_cp)*extra_cp*extra_cp; // 3
				double ss0_2_3_4 = extra_cp*(1-extra_cp)*(1-extra_cp); // 3
				double ss0_2_3_5 = extra_cp*(1-extra_cp)*extra_cp; // 3
				double ss0_2_4 = extra_cp*extra_cp; // 2
				
				ss_needed  = 4 * ss0_1_2_3_4 + 4 * ss0_1_2_3_5;
				ss_needed += 3 * (ss0_1_2_4 + ss0_1_3_4 + ss0_1_3_5 + ss0_2_3_4 + ss0_2_3_5);
				ss_needed += 2 * ss0_2_4;
				
				// how many finisher ends with 4 and 5 cps
				finisher_with_4cp = ss0_1_2_3_4 + ss0_1_2_4 + ss0_1_3_4 + ss0_2_3_4 + ss0_2_4;
				finisher_with_5cp = 1 - finisher_with_4cp;
				avg_cps_per_finisher = 4 + finisher_with_5cp;
				// lets try out the case we are always using ss when we are at 4 cps
				// so we will waste possible cps
				if(stats.getGearBuffs().has("combat_waste_cp")){
					ss_needed  = 5 * ss0_1_2_3_4 + 4 * ss0_1_2_3_5;
					ss_needed += 3 * ss0_1_3_5 + 3 * ss0_2_3_5;
					ss_needed += 4 * (ss0_1_2_4 + ss0_1_3_4 + ss0_2_3_4);
					ss_needed += 3 * ss0_2_4;
					
					finisher_with_4cp = 0.0;
					finisher_with_5cp = 1.0;
					avg_cps_per_finisher = 5.0;
				}
			}
		}
		double ss_ec = (40 - active_t15_4pc * get_t15_4pc(40)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double rvs_ec = (40 - active_t15_4pc * get_t15_4pc(40)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double evi_ec = (35 - active_t15_4pc * get_t15_4pc(35)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double rup_ec = (25 - active_t15_4pc * get_t15_4pc(25)) * (0.8 + 0.2/this.stats.getMhStyleHitChance());
		double snd_ec = 25 - active_t15_4pc * get_t15_4pc(25);
		double main_gauche_energy = get_avg_mg_energy();
		ss_ec -= main_gauche_energy;
		rvs_ec -= main_gauche_energy;
		evi_ec -= main_gauche_energy;
		rup_ec -= main_gauche_energy;
		
		double cpg_ec = ss_ec;
		if(stats.getGearBuffs().hasSet("t16_2pc")){
			ss_ec -= 10 * 0.2;
		}
		
		double avg_rupture_length = get_rupture_duration(avg_cps_per_finisher);
		double avg_snd_length = get_snd_duration(avg_cps_per_finisher);
		double avg_relentless_strikes = 5 * avg_cps_per_finisher;

		double energy_cost = ss_needed * cpg_ec;
		double energy_cost_rup = energy_cost + rup_ec - avg_relentless_strikes;
		double energy_cost_evi = energy_cost + evi_ec - avg_relentless_strikes;
		double energy_cost_snd = energy_cost + snd_ec - avg_relentless_strikes;
		double ruptures = rota_duration / avg_rupture_length;
		if(!stats.getGearBuffs().has("combat_use_rupture"))
			ruptures = 0.;
		if(shadow_blades && sync_ar_with_sb && !stats.getGearBuffs().has("combat_use_rupture_during_sync"))
			ruptures = 0.;
		double snds = rota_duration / avg_snd_length;
		double energy_left_for_evi = energy_available - ruptures * energy_cost_rup;
		energy_left_for_evi -= snds * energy_cost_snd;
		
		double attacks_so_far = (ruptures + snds) * (1 + ss_needed);
		double gcd = 1.0;
		if(shadow_blades && stats.getGearBuffs().hasSet("t15_4pc"))
			gcd -= 0.3;
		if(sync_ar_with_sb && stats.getGlyphs().contains("adrenaline_rush"))
			gcd -= 0.2;
		double time_left = rota_duration - attacks_so_far * gcd;
		double sum_attacks_per_evi = ss_needed + 1;
		double evis_time_limited = time_left / (sum_attacks_per_evi * gcd);
		evis_time_limited *= stats.getMhStyleHitChance();
		double evis_energy_limited = energy_left_for_evi / energy_cost_evi;
		
		double evis = evis_energy_limited;
		if(evis_time_limited < evis_energy_limited){
			evis = evis_time_limited;
			double energy_lost = energy_left_for_evi - evis * energy_cost_evi;
			//View.addConsole("EP "+ stats.calcEP + " - energy lost due to capping: " + energy_lost);
			String msg = "["+stats.calcEP + "] energy lost ";
			if(shadow_blades)
				msg += "during SB";
			else
				msg += "during normal rota";
			infoOutput.put(msg, energy_lost+"");
		}
		
		double ss_per_cycle = ss_needed * (snds + ruptures + evis);
		
		props.put("ss_per_cycle", ss_per_cycle);
		props.put("evi_per_cycle", evis);
		props.put("rup_per_cycle", ruptures);
		props.put("energy_cost_evi", energy_cost_evi);
		props.put("avg_cps_per_finisher", avg_cps_per_finisher);
		props.put("ss_per_finisher", ss_needed);
		props.put("finisher_with_4cp", finisher_with_4cp);
		props.put("finisher_with_5cp", finisher_with_5cp);
		props = combineProps(props,white_attacks);
		return props;
	}
	
	@SuppressWarnings("unchecked")
	private void calc_dps_combat4() {
		
		while(true) {
			double sb_duration = get_shadow_blades_duration();
			
			// rvs will never be used under shadowblades
			double rvs_duration = 24.;
			double rvs_ec = 40 * (0.8 + 0.2 / this.stats.getMhStyleHitChance());
			rvs_ec -= this.get_avg_mg_energy();
			double energy_regen = get_energy_regen();
			double min_energy_while_pooling = energy_regen;
		    double max_energy_while_pooling = 75.;
		    double average_pooling = Math.max(0, (max_energy_while_pooling - min_energy_while_pooling)) / 2;
			double rvs_every = rvs_duration + average_pooling / energy_regen;
			double rvs_interval = 1. / rvs_every;
			energy_regen -= rvs_ec / rvs_every;
			
			double rvs_per_sec = rvs_interval;
			double extra_evis_per_sec = rvs_per_sec * 1 / 5;
			
			// marked for death will never be used during shadowblads
			if(stats.hasTalent("marked_for_death")){
				extra_evis_per_sec += 1. / 60.;
			}
			double evi_ec = 35 * (0.8 + 0.2/this.stats.getMhStyleHitChance());
			energy_regen -= extra_evis_per_sec * (evi_ec - 5 * 5); // relentless strike
			
			double pooling_time = 0;
			if(stats.getGearBuffs().has("combat_pool_before_sb"))
				pooling_time = pooling_time(energy_regen, 100.);

			// AR
			
			double ar_duration = 15.;
			double sb_with_ar_dur = Math.min(sb_duration, ar_duration);
			double ar_dur_left = Math.max(ar_duration-sb_with_ar_dur, 0);

			double ar_energy_per_sec = 12. * (1. + 0.01 * stats.getBuffed(Stat.hst) / Stats.HASTE_CONVERSION_RATING);
			if(stats.getGearBuffs().has("combat_use_blade_flurry"))
				ar_energy_per_sec *= 0.8;
			
			// we will sync up ar with shadow blades
			// so under shadowblades we are having a lot more energy available
			// if ar_dur > sb_dur the rest will be distributed on the normal rotation
			boolean sync_ar_with_sb = stats.getGearBuffs().has("sync_ar_with_sb");
			double sb_ar_extra_energy = 0.0;
			double normal_ar_extra_energy = 0.0;
			if(sync_ar_with_sb){
				sb_ar_extra_energy = sb_with_ar_dur * ar_energy_per_sec;
				normal_ar_extra_energy = ar_dur_left * ar_energy_per_sec;
			}
			
			HashMap<String,Double> combat_sb;
			HashMap<String,Double> combat_normal;
			HashMap<String,Double> combat;
			
			// for shadow_blades up
			combat_sb = combat_rota(sb_duration, true, sb_with_ar_dur, sync_ar_with_sb, energy_regen, (pooling_time * energy_regen) + sb_ar_extra_energy);
			// for shadow_blades down
			combat_normal = combat_rota(sb_actual_cd-sb_duration, false, ar_dur_left, sync_ar_with_sb, energy_regen, (pooling_time * -energy_regen) + normal_ar_extra_energy);
			//combine
			combat = combineProps(combat_normal,combat_sb);
			
			// if we are not sync ar and sb 
			double ar_evis_per_sec = 0.0;
			double ar_ss_per_sec = 0.0;
			if(!sync_ar_with_sb){
				double ar_energy = ar_duration * ar_energy_per_sec;
				double evis_per_ar = ar_energy / combat_normal.get("energy_cost_evi");
				ar_evis_per_sec = evis_per_ar / ar_actual_cd;
				ar_ss_per_sec = ar_evis_per_sec * combat_normal.get("ss_per_finisher");
			}
			
			double ss_per_sec = combat.get("ss_per_cycle") / sb_actual_cd;
			double evis_per_sec = combat.get("evi_per_cycle") / sb_actual_cd;
			double rup_per_sec = combat.get("rup_per_cycle") / sb_actual_cd;
			if(stats.getGearBuffs().has("combat_use_rupture")){
				double rup_ticks_every = Math.min(0.5, rup_per_sec*12.);
				aps.put("rupture", rup_ticks_every);
			}
			
			finisher_with_4cp = sb_duration * combat_sb.get("finisher_with_4cp") + (sb_actual_cd-sb_duration) * combat_normal.get("finisher_with_4cp");
			finisher_with_4cp /= sb_actual_cd;
			finisher_with_5cp = sb_duration * combat_sb.get("finisher_with_5cp") + (sb_actual_cd-sb_duration) * combat_normal.get("finisher_with_5cp");
			finisher_with_5cp /= sb_actual_cd;
			double avg_finisher_cps = 4 + finisher_with_5cp;
			
			aps.put("sinister_strike", ss_per_sec + ar_ss_per_sec);
			aps.put("eviscerate", evis_per_sec + extra_evis_per_sec + ar_evis_per_sec);
			aps.put("revealing_strike", rvs_per_sec);
			
			aps.put("mh", (sb_actual_cd-sb_duration) * combat.get("mh") / sb_actual_cd);
			aps.put("oh", (sb_actual_cd-sb_duration) * combat.get("oh") / sb_actual_cd);
			aps.put("sb_mh", sb_duration * combat.get("sb_mh") / sb_actual_cd);
			aps.put("sb_oh", sb_duration * combat.get("sb_oh") / sb_actual_cd);
			
			double ks_aps = 7. / ks_actual_cd;
			aps.put("ks_mh", ks_aps);
			aps.put("ks_oh", ks_aps);
			
			//double mh_white_hits = aps.get("mh") + aps.get("sb_mh");
			double total_mh_hits = aps.get("mh") + aps.get("eviscerate") + aps.get("sb_mh");
			total_mh_hits += aps.get("sinister_strike") + aps.get("revealing_strike") + aps.get("ks_mh");
			double total_oh_hits = aps.get("oh") + aps.get("ks_oh") + aps.get("sb_oh");
			double mg_procs = this.stats.getMhStyleHitChance() * total_mh_hits * this.stats.getBuffed(Stat.mst) * 0.02;
			aps.put("main_gauche", mg_procs);
			
			if(Math.abs(total_hits - (total_mh_hits + total_oh_hits + mg_procs)) < PRECISION)
				break;

			total_hits = aps.get("mh") + aps.get("oh") + aps.get("eviscerate");
			total_hits += aps.get("sinister_strike") + aps.get("revealing_strike");
			total_hits += aps.get("ks_mh") + aps.get("ks_oh") + aps.get("main_gauche");
			total_hits += aps.get("sb_mh") +  aps.get("sb_oh");
			
			double ip_per_sec = (total_mh_hits + mg_procs) * 0.3 * stats.getMhPoisonHitChance();
			ip_per_sec += total_oh_hits * 0.3 * stats.getOhPoisonHitChance();
			
			aps.put("instant_poison", ip_per_sec);
			aps.put("deadly_poison", 1./3);
			
			double avg_reduction = 2 * avg_finisher_cps;
			double time_between_finisher = 1 / ( rup_per_sec + aps.get("eviscerate"));
			double real_cd_reduce_per_sec = 1 + avg_reduction / time_between_finisher;
			
			ks_actual_cd = ks_ori_cd / real_cd_reduce_per_sec;
			ar_actual_cd = ar_ori_cd / real_cd_reduce_per_sec;
			sb_actual_cd = sb_ori_cd / real_cd_reduce_per_sec;
			
			this.procs(total_mh_hits + mg_procs,total_oh_hits, ip_per_sec);
		}
		
		ks_uptime = 3.5 / ks_actual_cd;
		
		double bg_singleduration = 1 / (aps.get("sinister_strike") + aps.get("revealing_strike")) * 4;
		double bg_length = 15 + 3 * bg_singleduration;
		double bg_uptime_0_10_20 = bg_singleduration / bg_length;
		double bg_uptime_30 = 15 / bg_length;
		// this is for every other attack
		// optimizate would be to save for every single attack
		// e.g every single sinister strike which buffs are active
		// but that leads to an simulation
		avg_bg_buff = (0+0.1+0.2)*bg_uptime_0_10_20+0.3*bg_uptime_30;
		// with anticipation we can shift 1 evi from the 0 % buff to an 30% buff
		// so we assume that every 0% evi is with anticipation an 30% evi
		avg_bg_buff_eviscerate = avg_bg_buff;
		if(stats.hasTalent("anticipation")){
			avg_bg_buff_eviscerate += 0.3 * bg_uptime_30;
		}
	}
	
//	@SuppressWarnings("unused")
//	private void calc_dps_combat3() {	
//		int finisher_cps = 5;
//		double snd_length = 6 + finisher_cps * 6; // with 5 cp 36sec
//		if(stats.getGearBuffs().hasSet("t15_2pc"))
//			snd_length += 6.; // with 6 cps 42sec
//		
//		while(true) {
//			double sb_duration = get_shadow_blades_duration();
//			double sb_uptime = sb_duration / sb_actual_cd;
//			
//			double ss_rvs_ec = (40 - sb_uptime * get_t15_4pc(40)) * (0.8 + 0.2 / this.stats.getMhStyleHitChance());
//			double evi_ec = (35 - sb_uptime * get_t15_4pc(35)) * (0.8 + 0.2 / this.stats.getMhStyleHitChance());
//			double rup_ec = (25 - sb_uptime * get_t15_4pc(25)) * (0.8 + 0.2 / this.stats.getMhStyleHitChance());
//			double snd_ec = 25 - sb_uptime * get_t15_4pc(25);
//			
//			double cp_per_ss = 1.2 + sb_uptime;
//			double cp_per_rvs = 1 + sb_uptime;
//			
//			double ss_per_finisher = finisher_cps / cp_per_ss;
//			
//			double energy_regen = get_energy_regen();
//			double min_energy_while_pooling = energy_regen;
//		    double max_energy_while_pooling = 75.;
//		    double average_pooling = Math.max(0, (max_energy_while_pooling - min_energy_while_pooling)) / 2;
//			double rvs_every = 18. + average_pooling / energy_regen;
//			double rvs_interval = 1. / rvs_every;
//			energy_regen -= ss_rvs_ec / rvs_every;
//			
//			double energy_cost_built_up = ss_per_finisher * ss_rvs_ec;
//			double energy_cost_snd = energy_cost_built_up + snd_ec - 5 * finisher_cps;
//			double energy_cost_evi = energy_cost_built_up + evi_ec - 5 * finisher_cps;
//			double energy_cost_rup = energy_cost_built_up + rup_ec - 5 * finisher_cps;
//			double buildtime_snd = energy_cost_snd / energy_regen;
//			double buildtime_evi = energy_cost_evi / energy_regen;
//			double build_time_rup = energy_cost_rup / energy_regen;
//			double rup_per_snd = 0.;
//			double rupture_length = 24.;
//			if(stats.getGearBuffs().hasSet("t15_2pc"))
//				rupture_length += 4.;
//			if(stats.getGearBuffs().has("combat_use_rupture")){
//				rup_per_snd = snd_length / rupture_length; // 100% uptime
//				aps.put("rupture", 1./2);
//			}
//			
//			double timeleft_for_evi = snd_length - buildtime_snd - build_time_rup * rup_per_snd;
//			double evis_per_snd = timeleft_for_evi / buildtime_evi;
//			double ss_per_snd = (1 + rup_per_snd + evis_per_snd) * ss_per_finisher;
//			double rvs_per_snd = snd_length * rvs_interval;
//			
//			double evis_per_sec = evis_per_snd / snd_length;
//			double ss_per_sec = ss_per_snd / snd_length;
//			double rvs_per_sec = rvs_per_snd / snd_length;
//			
//			// AR
//			double attacks_for_aa_cp_regen = 0.;
//			if(aps.get("oh") != null)
//				attacks_for_aa_cp_regen += aps.get("oh") * stats.getOh().getSpeed()/1.4;
//			if(aps.get("main_gauche") != null){
//				
//				attacks_for_aa_cp_regen += mh_white_hits;
//			}
//			double autoattack_cp_regen = 15 * 0.2 * attacks_for_aa_cp_regen;
//			double ar_bonus_cp_regen = autoattack_cp_regen * 0.2; // ar gives 20% haste so 0.2
//			double ar_duration = 15;
//			double ar_energy = ar_duration * (ar_bonus_cp_regen + 12 * (1 + 0.01 * stats.getBuffed(Stat.hst) / Stats.HASTE_CONVERSION_RATING));
//			double evis_per_ar = ar_energy / energy_cost_evi;
//			double ar_evis_per_sec = evis_per_ar / ar_actual_cd;
//			double ar_ss_per_sec = ar_evis_per_sec * ss_per_finisher;
//			
//			double extra_evis_per_sec = rvs_per_sec * cp_per_rvs / 5;
//			
//			aps.put("sinister_strike", ss_per_sec + ar_ss_per_sec);
//			aps.put("eviscerate", evis_per_sec + ar_evis_per_sec + extra_evis_per_sec);
//			aps.put("revealing_strike", rvs_per_sec);
//		
//			this.white_attacks(sb_uptime);
//			
//			double ks_aps = 7. / ks_actual_cd;
//			aps.put("ks_mh", ks_aps);
//			aps.put("ks_oh", ks_aps);
//		
//			double total_mh_hits = aps.get("mh") + aps.get("eviscerate") + aps.get("sb_mh");
//			total_mh_hits += aps.get("sinister_strike") + aps.get("revealing_strike") + aps.get("ks_mh");
//			double total_oh_hits = aps.get("oh") + aps.get("ks_oh") + aps.get("sb_oh");
//			double mg_procs = this.stats.getMhStyleHitChance() * total_mh_hits * this.stats.getBuffed(Stat.mst) * 0.02;
//			aps.put("main_gauche", mg_procs);
//			
//			if(Math.abs(total_hits - (total_mh_hits + total_oh_hits + mg_procs)) < PRECISION)
//				break;
//
//			total_hits = aps.get("mh") + aps.get("oh") + aps.get("eviscerate");
//			total_hits += aps.get("sinister_strike") + aps.get("revealing_strike");
//			total_hits += aps.get("ks_mh") + aps.get("ks_oh") + aps.get("main_gauche");
//			total_hits += aps.get("sb_mh") +  aps.get("sb_oh");
//			
//			double ip_per_sec = (total_mh_hits + mg_procs) * 0.3 * stats.getMhPoisonHitChance();
//			ip_per_sec += total_oh_hits * 0.3 * stats.getOhPoisonHitChance();
//			
//			aps.put("instant_poison", ip_per_sec);
//			aps.put("deadly_poison", 1./3);
//			
//			double time_between_finisher = snd_length / (rup_per_snd + evis_per_snd + ar_evis_per_sec * snd_length);
//			double real_cd_reduce_per_sec = 1 + 10 / time_between_finisher;
//			
//			ks_actual_cd = ks_ori_cd / real_cd_reduce_per_sec;
//			ar_actual_cd = ar_ori_cd / real_cd_reduce_per_sec;
//			sb_actual_cd = sb_ori_cd / real_cd_reduce_per_sec;
//			
//			this.procs(total_mh_hits + mg_procs,total_oh_hits, ip_per_sec);
//		}
//		
//		ks_uptime = 3.5 / (ks_actual_cd);
//		
//		double bg_singleduration = 1 / (aps.get("sinister_strike") + aps.get("revealing_strike")) * 4;
//		double bg_length = 15 + 3 * bg_singleduration;
//		double bg_uptime_0_10_20 = bg_singleduration / bg_length;
//		double bg_uptime_30 = 15 / bg_length;
//		avg_bg_buff = (0+0.1+0.2)*bg_uptime_0_10_20+0.3*bg_uptime_30;
//	}
//	
//	@SuppressWarnings("unused")
//	private void calc_dps_combat2() {
//		double ss_rvs_ec = 40 * (0.8 + 0.2 / this.stats.getMhStyleHitChance());
//		double evi_ec = 35 * (0.8 + 0.2 / this.stats.getMhStyleHitChance());
//		double rup_ec = 25 * (0.8 + 0.2 / this.stats.getMhStyleHitChance());
//		double snd_ec = 25;
//		
//		int finisher_cps = 5;
//		double snd_length = 6 + finisher_cps * 6; // with 5 cp 36sec
//		
//		while(true) {
//			double sb_duration = get_shadow_blades_duration();
//			double sb_uptime = sb_duration / sb_actual_cd;
//						
//			double ss_cps = 1. + 0.2 + sb_uptime;
//			double rvs_cps = 1. + sb_uptime;
//			
//			double ss_only_per_finisher = finisher_cps / ss_cps;
//			double ss_per_finisher = (finisher_cps-rvs_cps) / ss_cps;
//			
//			double energy_regen = get_energy_regen();
//			
//			double cpgs_per_finisher = ss_only_per_finisher;
//			if(aps.get("sinister_strike") != null){
//				double ss_in18 = 18 * aps.get("sinister_strike");
//				cpgs_per_finisher = (ss_in18-1) * ss_only_per_finisher + ss_per_finisher;
//				cpgs_per_finisher /= ss_in18;
//			}
//			
//			double energy_per_snd = cpgs_per_finisher * ss_rvs_ec + snd_ec - finisher_cps * 5;
//			double energy_per_evi = cpgs_per_finisher * ss_rvs_ec + evi_ec - finisher_cps * 5;
//			double energy_per_rup = cpgs_per_finisher * ss_rvs_ec + rup_ec - finisher_cps * 5;
//			
//			double build_time_snd = energy_per_snd / energy_regen;
//			double build_time_evi = energy_per_evi / energy_regen;
//			double build_time_rup = energy_per_rup / energy_regen;
//			double rup_per_cycle = 0.;
//			if(stats.getGearBuffs().has("combat_use_rupture")){
//				rup_per_cycle = snd_length / 24.; // 24 rup length 100% uptime
//				aps.put("rupture", 1./2);
//			}
//			double cycle_time_left_for_evi = snd_length - build_time_snd - build_time_rup * rup_per_cycle;
//			double evis_per_cycle = cycle_time_left_for_evi / build_time_evi;
//			
//			double ss_per_cycle = cpgs_per_finisher * (1 + evis_per_cycle + rup_per_cycle);
//			
//			double ss_per_sec = ss_per_cycle / snd_length;
//			double ss_in18 = ss_per_sec * 18;
//			ss_per_sec = (ss_in18-1) / 18;
//			ss_per_cycle = ss_per_sec * snd_length;
//			ss_per_finisher = ss_per_cycle / (1 + evis_per_cycle + rup_per_cycle);
//			double evi_per_sec = evis_per_cycle / snd_length;
//			
//			// AR
//			double attacks_for_aa_cp_regen = 0.;
//			if(aps.get("oh") != null)
//				attacks_for_aa_cp_regen += aps.get("oh") * stats.getOh().getSpeed()/1.4;
//			if(aps.get("main_gauche") != null)
//				attacks_for_aa_cp_regen += aps.get("main_gauche");
//			double autoattack_cp_regen = 15 * 0.2 * attacks_for_aa_cp_regen;
//			double ar_bonus_cp_regen = autoattack_cp_regen * 0.2; // ar gives 20% haste so 0.2
//			double ar_duration = 15;
//			double ar_energy = ar_duration * (ar_bonus_cp_regen + 12 * (1 + 0.01 * stats.getBuffed(Stat.hst) / Stats.HASTE_CONVERSION_RATING));
//			double evis_per_ar = ar_energy / energy_per_evi;
//			double ar_evis_per_sec = evis_per_ar / ar_actual_cd;
//			double ar_ss_per_sec = ar_evis_per_sec * ss_per_finisher;
//			
//			aps.put("sinister_strike", ss_per_sec + ar_ss_per_sec);
//			aps.put("eviscerate", evi_per_sec + ar_evis_per_sec);
//			aps.put("revealing_strike", 1./18);
//		
//			this.white_attacks(sb_uptime);
//			
//			double ks_aps = 7. / ks_actual_cd;
//			aps.put("ks_mh", ks_aps);
//			aps.put("ks_oh", ks_aps);
//		
//			double total_mh_hits = aps.get("mh") + aps.get("eviscerate") + aps.get("sb_mh");
//			total_mh_hits += aps.get("sinister_strike") + aps.get("revealing_strike") + aps.get("ks_mh");
//			double total_oh_hits = aps.get("oh") + aps.get("ks_oh") + aps.get("sb_oh");
//			double mg_procs = this.stats.getMhStyleHitChance() * total_mh_hits * this.stats.getBuffed(Stat.mst) * 0.02;
//			aps.put("main_gauche", mg_procs);
//			
//			if(Math.abs(total_hits - (total_mh_hits + total_oh_hits + mg_procs)) < PRECISION)
//				break;
//
//			total_hits = aps.get("mh") + aps.get("oh") + aps.get("eviscerate");
//			total_hits += aps.get("sinister_strike") + aps.get("revealing_strike");
//			total_hits += aps.get("ks_mh") + aps.get("ks_oh") + aps.get("main_gauche");
//			total_hits += aps.get("sb_mh") +  aps.get("sb_oh");
//			
//			double ip_per_sec = (total_mh_hits + mg_procs) * 0.3 * stats.getMhPoisonHitChance();
//			ip_per_sec += total_oh_hits * 0.3 * stats.getOhPoisonHitChance();
//			
//			aps.put("instant_poison", ip_per_sec);
//			aps.put("deadly_poison", 1./3);
//			
//			double time_between_finisher = snd_length / (rup_per_cycle + evis_per_cycle + ar_evis_per_sec * snd_length);
//			double real_cd_reduce_per_sec = 1 + 10 / time_between_finisher;
//			
//			ks_actual_cd = ks_ori_cd / real_cd_reduce_per_sec;
//			ar_actual_cd = ar_ori_cd / real_cd_reduce_per_sec;
//			sb_actual_cd = sb_ori_cd / real_cd_reduce_per_sec;
//			
//			this.procs(total_mh_hits + mg_procs,total_oh_hits, ip_per_sec);
//		}
//		
//		ks_uptime = 3.5 / ks_actual_cd;
//		
//		double bg_singleduration = 1 / (aps.get("sinister_strike") + aps.get("revealing_strike")) * 4;
//		double bg_length = 15 + 3 * bg_singleduration;
//		double bg_uptime_0_10_20 = bg_singleduration / bg_length;
//		double bg_uptime_30 = 15 / bg_length;
//		avg_bg_buff = (0+0.1+0.2)*bg_uptime_0_10_20+0.3*bg_uptime_30;
//	}

/*	private void calc_dps_combat() {
		double ss_rvs_ec = 32 + 8 / this.stats.getMhStyleHitChance();
		double evi_ec = 28 + 7 / this.stats.getMhStyleHitChance();
		double rup_ec = 20 + 5 / this.stats.getMhStyleHitChance();
		double snd_ec = 25;
		
		int finisher_cps = 5;
		double snd_length = 6 + finisher_cps * 6; // with 5 cp 36sec
		
		while(true) {
			double sb_duration = 12.;
			if(stats.getGearBuffs().hasSet("t14_4pc"))
				sb_duration += 12;
			double sb_uptime = sb_duration / sb_actual_cd;
						
			//System.out.println(sb_uptime);
			double avg_cp_per_style = 1.2;
			if(aps.get("sinister_strike") != null){
				double ss_in18 = aps.get("sinister_strike") * 18;
				avg_cp_per_style = ((ss_in18-1)* 1.2 + 1 * 1) / ss_in18;
				avg_cp_per_style = (avg_cp_per_style + 1) * sb_uptime + avg_cp_per_style * (1 - sb_uptime);
			}
			
			double ss_per_finisher = finisher_cps / avg_cp_per_style;
			double energy_per_snd = ss_per_finisher * ss_rvs_ec + snd_ec - finisher_cps * 5;
			double energy_per_evi = ss_per_finisher * ss_rvs_ec + evi_ec - finisher_cps * 5;
			double energy_per_rup = ss_per_finisher * ss_rvs_ec + rup_ec - finisher_cps * 5;
			
			double energy_regen = get_energy_regen();
			
			double build_time_snd = energy_per_snd / energy_regen;
			double build_time_evi = energy_per_evi / energy_regen;
			double build_time_rup = energy_per_rup / energy_regen;
			double rup_per_cycle = 0.;
			if(stats.getGearBuffs().has("combat_use_rupture")){
				rup_per_cycle = snd_length / 24.; // 24 rup length 100% uptime
				aps.put("rupture", 1./2);
			}
			double cycle_time_left_for_evi = snd_length - build_time_snd - build_time_rup * rup_per_cycle;
			double evis_per_cycle = cycle_time_left_for_evi / build_time_evi;
			
			double ss_per_cycle = ss_per_finisher * (1 + evis_per_cycle + rup_per_cycle);
			
			double ss_per_sec = ss_per_cycle / snd_length;
			double ss_in18 = ss_per_sec * 18;
			ss_per_sec = (ss_in18-1) / 18;
			ss_per_cycle = ss_per_sec * snd_length;
			ss_per_finisher = ss_per_cycle / (1 + evis_per_cycle + rup_per_cycle);
			double evi_per_sec = evis_per_cycle / snd_length;
			
			// AR
			double attacks_for_aa_cp_regen = 0.;
			if(aps.get("oh") != null)
				attacks_for_aa_cp_regen += aps.get("oh") * stats.getOh().getSpeed()/1.4;
			if(aps.get("main_gauche") != null)
				attacks_for_aa_cp_regen += aps.get("main_gauche");
			double autoattack_cp_regen = 15 * 0.2 * attacks_for_aa_cp_regen;
			double ar_bonus_cp_regen = autoattack_cp_regen * 0.2; // ar gives 20% haste so 0.2
			double ar_duration = 15;
			double ar_energy = ar_duration * (ar_bonus_cp_regen + 12 * (1 + 0.01 * stats.getHasteBuffed() / Stats.HASTE_CONVERSION_RATING));
			double evis_per_ar = ar_energy / energy_per_evi;
			double ar_evis_per_sec = evis_per_ar / ar_actual_cd;
			double ar_ss_per_sec = ar_evis_per_sec * ss_per_finisher;
			
			// legendary evis
			double dagger_stage3 = 0;
			if(stats.getGearBuffs().hasSet("dagger_stage_3_2pc"))
				dagger_stage3 = 1;
			double avg_proc_at = 39.174; // stacks
			double legendary_valid_hits = total_hits;
			if(aps.get("instant_poison") != null)
				legendary_valid_hits += aps.get("instant_poison");
			double proc_interval = 1 / (0.09438 * legendary_valid_hits);
			//System.out.println("proc_interval " + proc_interval);
			double proc_after = avg_proc_at * proc_interval;
			double procs_per_sec = 1. / proc_after;
			double extra_evis_per_proc = 6;
			double avg_extra_evis = procs_per_sec*extra_evis_per_proc*dagger_stage3;
			avg_extra_evis = 0;
			
			aps.put("sinister_strike", ss_per_sec + ar_ss_per_sec);
			aps.put("eviscerate", evi_per_sec + ar_evis_per_sec + avg_extra_evis);
			aps.put("revealing_strike", 1./18);
		
			mh_swings_per_sec = get_total_speed()/stats.getMh().getSpeed();
			oh_swings_per_sec = get_total_speed()/stats.getOh().getSpeed();
			double sb_mh_swings_per_sec = mh_swings_per_sec * sb_uptime;
			double sb_oh_swings_per_sec = oh_swings_per_sec * sb_uptime;
			mh_swings_per_sec -= sb_mh_swings_per_sec;
			oh_swings_per_sec -= sb_oh_swings_per_sec;
			double mh_hits_per_sec = this.stats.getMhHitChance() * mh_swings_per_sec;
			double oh_hits_per_sec = this.stats.getOhHitChance() * oh_swings_per_sec;		
			double sb_mh_hits_per_sec = this.stats.getMhStyleHitChance() * sb_mh_swings_per_sec;
			double sb_oh_hits_per_sec = this.stats.getOhStyleHitChance() * sb_oh_swings_per_sec;
			
			aps.put("mh", mh_hits_per_sec);
			aps.put("oh", oh_hits_per_sec);
			aps.put("sb_mh", sb_mh_hits_per_sec);
			aps.put("sb_oh", sb_oh_hits_per_sec);
			
			double ks_aps = 7. / ks_actual_cd;
			aps.put("ks_mh", ks_aps);
			aps.put("ks_oh", ks_aps);
		
			double total_mh_hits = aps.get("mh") + aps.get("eviscerate") + aps.get("sb_mh");
			total_mh_hits += aps.get("sinister_strike") + aps.get("revealing_strike") + aps.get("ks_mh");
			double total_oh_hits = aps.get("oh") + aps.get("ks_oh") + aps.get("sb_oh");
			double mg_procs = this.stats.getMhStyleHitChance() * total_mh_hits * this.stats.getMasteryBuffed() * 0.02;
			aps.put("main_gauche", mg_procs);
			
			if(Math.abs(total_hits - (total_mh_hits + total_oh_hits + mg_procs)) < PRECISION)
				break;

			aps.put("deadly_poison", 1./3);
			total_hits = aps.get("mh") + aps.get("oh") + aps.get("eviscerate");
			total_hits += aps.get("sinister_strike") + aps.get("revealing_strike");
			total_hits += aps.get("ks_mh") + aps.get("ks_oh") + aps.get("main_gauche");
			total_hits += aps.get("sb_mh") +  aps.get("sb_oh");
			
			double ip_per_sec = (total_mh_hits + mg_procs) * 0.3 * stats.getMhPoisonHitChance();
			ip_per_sec += total_oh_hits * 0.3 * stats.getOhPoisonHitChance();
			
			aps.put("instant_poison", ip_per_sec);
			
			double time_between_finisher = snd_length / (rup_per_cycle + evis_per_cycle + (ar_evis_per_sec + avg_extra_evis) * snd_length);
			double real_cd_reduce_per_sec = 1 + 10 / time_between_finisher;
			
			ks_actual_cd = ks_ori_cd / real_cd_reduce_per_sec;
			ar_actual_cd = ar_ori_cd / real_cd_reduce_per_sec;
			sb_actual_cd = sb_ori_cd / real_cd_reduce_per_sec;
			
			this.procs(total_mh_hits + mg_procs,total_oh_hits);
		}
		
		ks_uptime = 3.5 / ks_actual_cd;
		
		double bg_singleduration = 1 / (aps.get("sinister_strike") + aps.get("revealing_strike")) * 4;
		double bg_length = 15 + 3 * bg_singleduration;
		double bg_uptime_0_10_20 = bg_singleduration / bg_length;
		double bg_uptime_30 = 15 / bg_length;
		avg_bg_buff = (0+0.1+0.2)*bg_uptime_0_10_20+0.3*bg_uptime_30;
	}*/
	
	@SuppressWarnings("unused")
	private void poison_procs(double total_mh_hits, double total_oh_hits){
		double proc_chance = 0.3;
		double ip_per_sec = (total_mh_hits) * proc_chance * stats.getMhPoisonHitChance();
		ip_per_sec += total_oh_hits * proc_chance * stats.getOhPoisonHitChance();
		
		aps.put("instant_poison", ip_per_sec);
		aps.put("deadly_poison", 1./3);
	}
	
	private HashMap<String,Double> white_attacks(boolean shadow_blades){
		if(stats.getSpec() == Spec.Combat){
			System.out.println("error: spec wrong");
			return null;
		}
		HashMap<String,Double> props = new HashMap<String,Double>();
		
		double total_speed = get_total_speed();

		if(shadow_blades){
			double mh_swings_per_sec = total_speed/stats.getMh().getSpeed();
			double oh_swings_per_sec = total_speed/stats.getOh().getSpeed();
			double mh_hits_per_sec = this.stats.getMhStyleHitChance() * mh_swings_per_sec;
			double oh_hits_per_sec = this.stats.getOhStyleHitChance() * oh_swings_per_sec;
			props.put("sb_mh", mh_hits_per_sec);
			props.put("sb_oh", oh_hits_per_sec);
		}else{
			mh_swings_per_sec = total_speed/stats.getMh().getSpeed();
			oh_swings_per_sec = total_speed/stats.getOh().getSpeed();
			double mh_hits_per_sec = this.stats.getMhHitChance() * mh_swings_per_sec;
			double oh_hits_per_sec = this.stats.getOhHitChance() * oh_swings_per_sec;
			props.put("mh", mh_hits_per_sec);
			props.put("oh", oh_hits_per_sec);
		}
		
		return props;
	}
	
	private HashMap<String,Double> combat_white_attacks(boolean shadow_blades, double ar_uptime){
		if(stats.getSpec() != Spec.Combat){
			System.out.println("error: spec wrong");
			return null;
		}
		HashMap<String,Double> props = new HashMap<String,Double>();
		
		double total_speed = get_combat_total_speed(ar_uptime);

		if(shadow_blades){
			double mh_swings_per_sec = total_speed/stats.getMh().getSpeed();
			double oh_swings_per_sec = total_speed/stats.getOh().getSpeed();
			double mh_hits_per_sec = this.stats.getMhStyleHitChance() * mh_swings_per_sec;
			double oh_hits_per_sec = this.stats.getOhStyleHitChance() * oh_swings_per_sec;
			props.put("sb_mh", mh_hits_per_sec);
			props.put("sb_oh", oh_hits_per_sec);
		}else{
			mh_swings_per_sec = total_speed/stats.getMh().getSpeed();
			oh_swings_per_sec = total_speed/stats.getOh().getSpeed();
			double mh_hits_per_sec = this.stats.getMhHitChance() * mh_swings_per_sec;
			double oh_hits_per_sec = this.stats.getOhHitChance() * oh_swings_per_sec;
			props.put("mh", mh_hits_per_sec);
			props.put("oh", oh_hits_per_sec);
		}
		
		return props;
	}
	
	private void white_attacks(double sb_uptime){
		mh_swings_per_sec = get_total_speed()/stats.getMh().getSpeed();
		oh_swings_per_sec = get_total_speed()/stats.getOh().getSpeed();
		double sb_mh_swings_per_sec = mh_swings_per_sec * sb_uptime;
		double sb_oh_swings_per_sec = oh_swings_per_sec * sb_uptime;
		
		// sync ar and sb so correct implemented?
		if(stats.getSpec() == Spec.Combat && stats.getGearBuffs().has("sync_ar_with_sb")){
			double sb_duration = get_shadow_blades_duration();
			double ar_duration = 15.;
			sb_mh_swings_per_sec *= 1 + 0.2 * Math.min(1.0, sb_duration / ar_duration);
			sb_oh_swings_per_sec *= 1 + 0.2 * Math.min(1.0, sb_duration / ar_duration);
		}
		
		mh_swings_per_sec -= sb_mh_swings_per_sec;
		oh_swings_per_sec -= sb_oh_swings_per_sec;
		double mh_hits_per_sec = this.stats.getMhHitChance() * mh_swings_per_sec;
		double oh_hits_per_sec = this.stats.getOhHitChance() * oh_swings_per_sec;		
		double sb_mh_hits_per_sec = this.stats.getMhStyleHitChance() * sb_mh_swings_per_sec;
		double sb_oh_hits_per_sec = this.stats.getOhStyleHitChance() * sb_oh_swings_per_sec;
		
		aps.put("mh", mh_hits_per_sec);
		aps.put("oh", oh_hits_per_sec);
		aps.put("sb_mh", sb_mh_hits_per_sec);
		aps.put("sb_oh", sb_oh_hits_per_sec);
	}
	
	private void init_assassination(){
		double sb_duration = get_shadow_blades_duration();
		double vendetta_duration = 20.;
		double vendetta_dmg = 0.3;
		if(stats.hasGlyph("vendetta")){
			vendetta_duration += 10.;
			vendetta_dmg -= 0.05;
		}
		double vendetta_uptime = vendetta_duration / 120.;
		vendetta_mult = 1 + vendetta_dmg * vendetta_uptime;
    
        double vendetta_shadow_blades_overlap = Math.min(sb_duration, vendetta_duration);
        double vendetta_uptime_during_shadow_blades = .5 * vendetta_shadow_blades_overlap / sb_duration;
        shadow_blades_vendetta_mult = 1 + vendetta_dmg * vendetta_uptime_during_shadow_blades;
        
        double autoattack_duration = sb_actual_cd - sb_duration;
        double autoattack_vendetta_overlap = sb_actual_cd * vendetta_uptime - sb_duration * vendetta_uptime_during_shadow_blades;
        autoattack_vendetta_mult = 1 + vendetta_dmg * autoattack_vendetta_overlap / autoattack_duration;
	}
	
	private double get_energy_regen(){
		double base_regen = 10.;
		if(stats.getSpec() == Spec.Combat)
			base_regen = 12.;
		if(stats.getSpec() == Spec.Combat && stats.getGearBuffs().has("combat_use_blade_flurry"))
			base_regen *= 0.8;
		double regen = base_regen * (1.0 + 0.01 * stats.getBuffed(Stat.hst) / Stats.HASTE_CONVERSION_RATING);

		if(stats.getGearBuffs().has("berserking"))
			regen += base_regen * 0.2 * 10 / 180.;
		if(stats.getGearBuffs().has("arcane_torrent"))
			regen += 15. * 1. / 120;
		
		// This will be calculated directly for every phase because sb and not sb
		// giving much different energy so umcommented
//		if(stats.getSpec() == Spec.Combat){
//			double proc_chance = stats.getOh().getSpeed()/1.4 * 0.2;
//			double proc_chance_mh = 0.2;
//			double cp_regen = this.stats.getOhHitChance() * 15. * get_total_speed()/stats.getOh().getSpeed() * proc_chance;
//			double proc_chance_mg = this.stats.getBuffed(Stat.mst) * 0.02;
//			cp_regen += mh_white_hits * proc_chance_mg * 15. * proc_chance_mh;
//			System.out.println("mg_regen normal " +cp_regen);
//			regen += cp_regen;
//		}
		if(stats.getSpec() == Spec.Assassination){
			double vw_regen = 0.5 * 0.75 * 10;
			regen += vw_regen;
		}
		if(stats.getSpec() == Spec.Subtlety){
			double er_regen = 0.5 * 8;
			regen += er_regen;
		}
		if(stats.getGearBuffs().has("use_and_get_tott")){
			double tott = - 15. / (30. + reaction_time);
			regen += tott;
		}
		return regen;
	}
	
	protected double get_combat_total_speed(double ar_uptime){
		double snd_speed = 1.4;
		if(stats.getSpec() == Spec.Subtlety){
			snd_speed = 1. +  0.4 * (1 + .03 * this.stats.getBuffed(Stat.mst));
		}
		double speed = snd_speed * (1 + 0.01 * stats.getBuffed(Stat.hst) / Stats.HASTE_CONVERSION_RATING);
		if(stats.getBuffs().hasBuff("attackspeed_buff"))
			speed *= 1.1;
		if(stats.getGearBuffs().has("berserking"))
			speed *= 1 + .2 * 10. / 180;
		if(stats.getGearBuffs().has("time_is_money"))
            speed *= 1.01;

		speed *= 1 + 0.2 * ar_uptime;
		
		speed *= (1 + 0.3 * 40 / FIGHT_LENGTH);
		return speed;
	}
	
	protected double get_total_speed(){
		double snd_speed = 1.4;
		if(stats.getSpec() == Spec.Subtlety){
			snd_speed = 1. +  0.4 * (1 + .03 * this.stats.getBuffed(Stat.mst));
		}
		double speed = snd_speed * (1 + 0.01 * stats.getBuffed(Stat.hst) / Stats.HASTE_CONVERSION_RATING);
		if(stats.getBuffs().hasBuff("attackspeed_buff"))
			speed *= 1.1;
		if(stats.getSpec() == Spec.Combat /*&& !stats.getGearBuffs().has("sync_ar_with_sb")*/){
			//speed *= (1 + 0.2 * 15 / ar_actual_cd);
		}
		if(stats.getGearBuffs().has("berserking"))
			speed *= 1 + .2 * 10. / 180;
		if(stats.getGearBuffs().has("time_is_money"))
            speed *= 1.01;
		
		speed *= (1 + 0.3 * 40 / FIGHT_LENGTH);
		return speed;
	}
		
	private double get_eff_dmg(){
		return get_eff_dmg(Stats.BOSS_ARMOR);
	}
	//
	private double get_eff_dmg(double armor){
		double mod = 1;
		if(stats.getBuffs().hasBuff("armor_debuff"))
			mod -= 0.12;
		double boss_armor = armor * mod;
		double param = 90 * 4037.5 - 317117.5; // LEVEL_90 player and target 93
		return param / (boss_armor + param);
	}
	
	private HashMap<Integer,Double> get_cp_per_cpg(List<Double> list){
        // Computes the combined probabilites of getting an additional cp from
        // each of the items in probs.
		HashMap<Integer,Double> cp_per_cpg = new HashMap<Integer,Double>();
		cp_per_cpg.put(1, 1.0);
        for(double prob : list){
            if(prob == 0)
            	continue;
            HashMap<Integer,Double> new_cp_per_cpg = new HashMap<Integer,Double>();
            for(int cp : cp_per_cpg.keySet()){
            	if(!new_cp_per_cpg.containsKey(cp)) new_cp_per_cpg.put(cp, 0.0);
                if(!new_cp_per_cpg.containsKey(cp + 1)) new_cp_per_cpg.put(cp + 1, 0.0);
                new_cp_per_cpg.put(cp, new_cp_per_cpg.get(cp) + cp_per_cpg.get(cp) * (1 - prob));
                new_cp_per_cpg.put(cp + 1, new_cp_per_cpg.get(cp + 1) + cp_per_cpg.get(cp) * prob);
            }
            cp_per_cpg = new_cp_per_cpg;
        }
        return cp_per_cpg;
	}

	private HashMap<String,Double> combineProps(HashMap<String,Double>... props){
		HashMap<String,Double> ret = new HashMap<String,Double>();
		for(HashMap<String,Double> propMap : props){
			for(Entry<String,Double> prop : propMap.entrySet()){
				if(!ret.containsKey(prop.getKey())) ret.put(prop.getKey(), 0.0);
				ret.put(prop.getKey(), ret.get(prop.getKey()) + prop.getValue());
			}
		}
		return ret;
	}
	
	private double get_t15_4pc(double energy_cost){
		if(!stats.getGearBuffs().hasSet("t15_4pc")){
			return 0;
		}
		return energy_cost * 0.4;
	}
	private double get_snd_duration(double cps){
		double dur = 6 + 6 * cps;
		if(stats.getGearBuffs().hasSet("t15_2pc"))
			dur += 6;
		return dur;
	}
	private double get_rupture_duration(double cps){
		double dur = 4 + 4 * cps;
		if(stats.getGearBuffs().hasSet("t15_2pc"))
			dur += 4;
		return dur;
	}
	private double get_shadow_blades_duration(){
		double duration = 12.;
		if(stats.getGearBuffs().hasSet("t14_4pc")){
			duration += 12;
			if(stats.getSpec() == Spec.Combat){
				duration -= 6;
			}
		}
		return duration;
	}
	
	private double get_avg_mg_energy(){
		double proc_chance_cp = 0.2;
		double proc_chance_mg = this.stats.getBuffed(Stat.mst) * 0.02;
		double regen = proc_chance_mg * 15. * proc_chance_cp * stats.getMhStyleHitChance();
		return regen;
	}
	
	private double pooling_time(double energy_regen, double min_energy, double max_energy){
		double min = energy_regen;
		double max = max_energy - (2 + reaction_time) * energy_regen;
		double avg = (min + max) / 2 / energy_regen;
		return avg;
	}
	private double pooling_time(double energy_regen, double max_energy){
		double min = energy_regen;
		return pooling_time(energy_regen, min, max_energy);
	}
	private double get_avg_cp_per_cpg(HashMap<Integer,Double> cp_per_cpg){
		double avg = 0;
		for(int cp : cp_per_cpg.keySet()){
			avg += cp * cp_per_cpg.get(cp);
		}
		return avg;
	}
	
	private void calc_dps_contribution(AttacksBlock ab,double crit_chance){
		double hitDamage = ab.getHitDamage();
		double critDamage = ab.getCritDamage();
		
		double dmgMod = 1.0;
		if(stats.getGearBuffs().has("use_and_get_tott")){
			dmgMod *= (1 + 0.15 * 6. / (30 + reaction_time));
		}
		if(ab.getType() == AttackType.physical){
			if(stats.getBuffs().hasBuff("physicaldmg_debuff"))
				dmgMod *= 1.04;
			dmgMod *= get_eff_dmg();
		}
		if(ab.getType() == AttackType.magical){
			if(stats.getBuffs().hasBuff("spelldmg_debuff"))
				dmgMod *= 1.05;
		}
		if(stats.getSpec() == Spec.Combat){
			if(ab.getAttack().equals("eviscerate")){
				dmgMod *= (1 + avg_bg_buff_eviscerate);
			}else
				dmgMod *= (1 + avg_bg_buff);
			if(Arrays.asList( "instant_poison","mh","oh","main_gauche","sb_mh","sb_oh" ).contains( ab.getAttack() )){
				dmgMod *= (1 + 0.5 * ks_uptime);
			}
		}
		if(stats.getSpec() == Spec.Assassination){
			dmgMod *= 1.25; // passive assassins resolve
			if(Arrays.asList("sb_mh","sb_oh").contains(ab.getAttack())){
				dmgMod *= this.shadow_blades_vendetta_mult;
			}
			else if(Arrays.asList("mh","oh").contains(ab.getAttack())){
				dmgMod *= this.autoattack_vendetta_mult;
			}
			else if(!ab.getAttack().contains("elemental_force"))
				dmgMod *= this.vendetta_mult;
			
			if(Arrays.asList("instant_poison","deadly_poison","envenom","venomous_wound").contains( ab.getAttack() )){
				dmgMod *= (1 + this.stats.getBuffed(Stat.mst) * 0.035);
			}
		}
		if(stats.getSpec() == Spec.Subtlety){
			dmgMod *= 1.2; // Sanguinary Vein
			dmgMod *= mos_multiplier;
			if(Arrays.asList("ambush").contains( ab.getAttack() )){
				dmgMod *= this.find_weakness_damage_boost;
				dmgMod *= 1 + .1 * ambushes_from_vanish / aps.get("ambush");
				if(stats.hasTalent("nightstalker"))
					dmgMod *= 1 + .25 * ambushes_from_vanish / aps.get("ambush");				
			}else if(ab.getType() == AttackType.physical){
				dmgMod *= this.find_weakness_multiplier;
			}
			if(Arrays.asList("eviscerate","rupture").contains( ab.getAttack() )){
				dmgMod *= (1 + this.stats.getBuffed(Stat.mst) * 0.03);
			}
		}
		hitDamage *= dmgMod;
		critDamage *= dmgMod;
		
		double dpsAll = 0.0;
		double dpsAllOnlyCrit = 0.0;
		if(ab.getAttack().equals("mh")){
			double hit = hitDamage * (stats.getMhHitChance() - 0.24 - crit_chance);
			double glancing = hitDamage * 0.75 * 0.24;
			double crit = critDamage * crit_chance;
			dpsAll = (hit + crit + glancing) * mh_swings_per_sec;
			dpsAllOnlyCrit = crit * mh_swings_per_sec;
		}else if(ab.getAttack().equals("oh")){
			double hit = hitDamage * (stats.getOhHitChance() - 0.24 - crit_chance);
			double glancing = hitDamage * 0.75 * 0.24;
			double crit = critDamage * crit_chance;
			dpsAll = (hit + crit + glancing) * oh_swings_per_sec;
			dpsAllOnlyCrit =  crit * oh_swings_per_sec;
		}else{
			double hit = hitDamage * ( 1 - crit_chance );
			double glancing = 0;
			double crit = critDamage * crit_chance;
			dpsAll = (hit + crit + glancing) * aps.get(ab.getAttack());
			dpsAllOnlyCrit =  crit * aps.get(ab.getAttack());
		}
		dps.put(ab.getAttack(), dpsAll);
		dpsOnlyCrit.put(ab.getAttack(), dpsAllOnlyCrit);
		
		if(stats.getSpec() == Spec.Combat 
				&& stats.getGearBuffs().has("combat_use_blade_flurry")
				&& combat_blade_flurry_attacks.contains(ab.getAttack())){
			double bf_targets = 4; // max 4
			double dps_mod = 0.4;
			if(dps.get("blade_flurry") == null)
				dps.put("blade_flurry", 0.0);
			if(dpsOnlyCrit.get("blade_flurry") == null)
				dpsOnlyCrit.put("blade_flurry", 0.0);
			dps.put("blade_flurry", dps.get("blade_flurry") + dps_mod * bf_targets * dpsAll);
			dpsOnlyCrit.put("blade_flurry", dpsOnlyCrit.get("blade_flurry") + dps_mod * bf_targets * dpsAllOnlyCrit);
		}
	}

	@Override
	public Map<String, Double> getEP() {
		return ep;
	}
}
